ホームページ  >  記事  >  Java  >  Javaの3大機能のうちポリモーフィズムについての深い理解

Javaの3大機能のうちポリモーフィズムについての深い理解

高洛峰
高洛峰オリジナル
2017-01-19 14:12:031259ブラウズ

Java の 3 つの主要な特徴

オブジェクト指向プログラミングには、カプセル化、継承、ポリモーフィズムという 3 つの主要な特徴があります。

カプセル化は、クラスの内部実装メカニズムを隠蔽し、その使用に影響を与えることなくクラスの内部構造を変更することができ、またデータを保護します。内部の詳細のみが外界から隠蔽され、アクセス方法のみが外界に公開されます。

継承とは、親クラスのコードを再利用することです。 2 つのクラス間に IS-A 関係がある場合、継承を使用できます。 、継承もポリモーフィズムの実現への道を開きます。では、ポリモーフィズムとは何でしょうか?ポリモーフィズムの実装メカニズムは何ですか?私がそれを 1 つずつ明らかにするのを見てください:

いわゆるポリモーフィズムとは、プログラム内で定義された参照変数が指す特定の型と、参照変数を介して発行されるメソッド呼び出しが、プログラミング中に決定されるのではなく、プログラミング中に決定されることを意味します。プログラムの実行時にのみ、参照変数がどのクラス インスタンス オブジェクトを指すかが決定されます。これは、実行中に決定される必要があります。プログラムの。特定のクラスはプログラムの実行時にのみ決定されるため、ソース プログラム コードを変更せずに参照変数をさまざまなクラス実装にバインドでき、参照によって呼び出される特定のメソッドがそれに応じて変更されます。つまり、参照変数は変更する必要がありません。プログラム コードは、実行中にプログラムにバインドされている特定のコードを変更することができ、これによりプログラムが複数の実行状態を選択できるようになります。

たとえば、あなたはワインの神様で、ワインが特に好きだとします。ある日、家に帰ると、テーブルの上に白ワインが何杯も置かれていました。それが何のワインであるかは、飲んでみて初めてわかりました。これを飲むと建南春、もう一度飲むと五稜郭、また飲むと九桂酒… ここでは次のように表すことができます:

ワイン a = 建南春

ワイン b = 五梁渓

ワインc = Jiuguijiu

ここで表現されているのはポリモーフィズムです。 Jiannanchun、Wuliangye、および Jiuguijiu はすべて wine のサブクラスであり、wine の親クラスを通じてさまざまなサブクラスを参照できます。これはポリモーフィズムです。実行時に参照変数の特定のポイントのみがわかります。

確かに、ポリモーフィズムを理解するには、「上方変換」とは何かを理解する必要があります。継承では、上方変換を簡単に紹介しました。簡単に説明します。上記の飲酒の例では、Wine (Win) が親クラスであり、Jiannanchun (JNC)、Wulianye (WLY)、および Jiuguijiu (JGJ) がサブクラスです。次のコードを定義します:

JNC a = new JNC();

このコードは、Jian Nanchun オブジェクトをインスタンス化するだけなので、非常に簡単に理解できます。しかし、これはどうでしょうか?

Wine a = new JNC();

ここでこれを理解すると、ここでは、JNC オブジェクト インスタンスを指す Wine タイプ a が定義されています。 JNC は Wine から継承しているため、JNC を Wine に自動的に変換できるため、 は JNC インスタンス オブジェクトを指すことができます。これを行うことには非常に大きな利点があります。サブクラスを指す親クラスの参照型を定義すると、サブクラスが親クラスの拡張であることがわかります。親クラスの共通機能を参照できることに加えて、サブクラスの強力な機能も使用できます。

しかし、上方変換にはいくつかの欠点があります。つまり、確実に一部のメソッドとプロパティが失われ、それらを取得できなくなります。したがって、親クラス型の参照は、親クラスで定義されているすべてのプロパティとメソッドを呼び出すことができますが、サブクラスにのみ存在するメソッドとプロパティにはアクセスできません---1。

public class Wine {
 public void fun1(){
  System.out.println("Wine 的Fun.....");
  fun2();
 }
  
 public void fun2(){
  System.out.println("Wine 的Fun2...");
 }
}
 
public class JNC extends Wine{
 /**
  * @desc 子类重载父类方法
  *  父类中不存在该方法,向上转型后,父类是不能引用该方法的
  * @param a
  * @return void
  */
 public void fun1(String a){
  System.out.println("JNC 的 Fun1...");
  fun2();
 }
  
 /**
  * 子类重写父类方法
  * 指向子类的父类引用调用fun2时,必定是调用该方法
  */
 public void fun2(){
  System.out.println("JNC 的Fun2...");
 }
}
 
public class Test {
 public static void main(String[] args) {
  Wine a = new JNC();
  a.fun1();
 }
}
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...

プログラムの実行結果から、a.fun1() は最初に親クラス Wine で fun1() を実行し、次にサブクラス JNC で fun2() を実行することがわかりました。

分析: このプログラムでは、サブクラス JNC が親クラス Wine のメソッド fun1() をオーバーロードし、fun2() を書き換えます。また、オーバーロードされた fun1(String a) と fun1() は同じメソッドではありません。クラス内にはそのようなメソッドは存在せず、上方変換後にメソッドが失われるため、JNCを実行するWine型参照ではfun1(String a)メソッドを参照できません。サブクラス JNC は fun2() を書き換えるため、JNC を指す Wine 参照は JNC の fun2() メソッドを呼び出します。

したがって、ポリモーフィズムを次のように要約できます:

サブクラスを指す親クラス参照は上方に変換され、親クラスが所有するメソッドとプロパティにのみアクセスできます。これらは、サブクラスには存在するが、親クラスには存在しません。親クラスのメソッドをオーバーロードしているにもかかわらず、この参照は使用できません。サブクラスが親クラスの一部のメソッドをオーバーライドする場合、これらのメソッドを呼び出すときは、サブクラスで定義されたメソッド (動的接続、動的呼び出し) を使用する必要があります。

オブジェクト指向のみの場合、ポリモーフィズムはコンパイル時ポリモーフィズムと実行時ポリモーフィズムに分類されます。このうち、編集時のポリモーフィズムは、主にメソッドのオーバーロードを指します。編集後は、実行時に 2 つの異なる関数になります。実行時のポリモーフィズムは動的であり、動的バインディングによって実現されます。これをポリモーフィズムと呼びます。

ポリモーフィズムの実装

2.1 実装条件

在刚刚开始就提到了继承在为多态的实现做了准备。子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。

Java实现多态有三个必要条件:继承、重写、向上转型。

      继承:在多态中必须存在有继承关系的子类和父类。

      重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

      向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

2.2实现形式

在Java中有两种形式可以实现多态。继承和接口。

2.2.1、基于继承实现的多态

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

public class Wine {
 private String name;
  
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 public Wine(){
 }
  
 public String drink(){
  return "喝的是 " + getName();
 }
  
 /**
  * 重写toString()
  */
 public String toString(){
  return null;
 }
}
 
public class JNC extends Wine{
 public JNC(){
  setName("JNC");
 }
  
 /**
  * 重写父类方法,实现多态
  */
 public String drink(){
  return "喝的是 " + getName();
 }
  
 /**
  * 重写toString()
  */
 public String toString(){
  return "Wine : " + getName();
 }
}
 
public class JGJ extends Wine{
 public JGJ(){
  setName("JGJ");
 }
  
 /**
  * 重写父类方法,实现多态
  */
 public String drink(){
  return "喝的是 " + getName();
 }
  
 /**
  * 重写toString()
  */
 public String toString(){
  return "Wine : " + getName();
 }
}
 
public class Test {
 public static void main(String[] args) {
  //定义父类数组
  Wine[] wines = new Wine[2];
  //定义两个子类
  JNC jnc = new JNC();
  JGJ jgj = new JGJ();
   
  //父类引用子类对象
  wines[0] = jnc;
  wines[1] = jgj;
   
  for(int i = 0 ; i < 2 ; i++){
   System.out.println(wines[i].toString() + "--" + wines[i].drink());
  }
  System.out.println("-------------------------------");
 
 }
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------

在上面的代码中JNC、JGJ继承Wine,并且重写了drink() 、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:

Object o = new JGJ();
 
  System.out.println(o.toString());

 输出的结果是Wine : JGJ。

Object、Wine、JGJ三者继承链关系是:JGJ—>Wine—>Object。所以我们可以这样说:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。

但是注意如果这样写:

Object o = new Wine();
 
System.out.println(o.toString());

输出的结果应该是Null,因为JGJ并不存在于该对象继承链中。

所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

2.2.2、基于接口实现的多态

继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

三、经典实例。

通过上面的讲述,可以说是对多态有了一定的了解。现在趁热打铁,看一个实例。该实例是有关多态的经典例子,摘自:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。

public class A {
 public String show(D obj) {
  return ("A and D");
 }
 
 public String show(A obj) {
  return ("A and A");
 }
 
}
 
public class B extends A{
 public String show(B obj){
  return ("B and B");
 }
  
 public String show(A obj){
  return ("B and A");
 }
}
 
public class C extends B{
 
}
 
public class D extends B{
 
}
 
public class Test {
 public static void main(String[] args) {
  A a1 = new A();
  A a2 = new B();
  B b = new B();
  C c = new C();
  D d = new D();
   
  System.out.println("1--" + a1.show(b));
  System.out.println("2--" + a1.show(c));
  System.out.println("3--" + a1.show(d));
  System.out.println("4--" + a2.show(b));
  System.out.println("5--" + a2.show(c));
  System.out.println("6--" + a2.show(d));
  System.out.println("7--" + b.show(b));
  System.out.println("8--" + b.show(c));
  System.out.println("9--" + b.show(d)); 
 }
}

运行结果:

1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

在这里看结果1、2、3还好理解,从4开始就开始糊涂了,对于4来说为什么输出不是“B and B”呢?

首先我们先看一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O) 、super.show(O)、this.show((super)O) 、super.show((super)O) 。

分析:

从上面的程序中我们可以看出A、B、C、D存在如下关系。

最初に 5、a2.show(c) を分析します。a2 は型 A の参照変数であるため、これは A を表します、a2.show(c)、クラス A には見つからないため、A のスーパーに移動しますクラス内で (super) を検索します。 A にはスーパークラス (Object を除く) がないため、this.show((super)O) という第 3 レベルにジャンプします。 O B と A の場合、これも A です。A には Show(A obj) が見つかります。同時に、a2 はクラス B の参照であり、クラス B は show(A obj) をオーバーライドするため、最終的にサブクラス B が呼び出されます。クラスの show(A obj) メソッド、結果は B と A です。

他の回答も同様の方法で確認できます。

メソッドは見つかりましたが、ここで少し疑問が残ります。次の文を見てみましょう。スーパークラス オブジェクト参照変数がサブクラス オブジェクトを参照する場合、参照変数の型ではなく、参照されるオブジェクトの型が参照されます。誰が呼び出されるかを決定しますが、呼び出されるメソッドはスーパークラス、つまりサブクラスによってオーバーライドされるメソッドで定義されている必要があります。この文の意味を説明するために例を使用してみましょう: a2.show(b);

ここで、a2 はタイプ A の参照変数です。これは B オブジェクトを参照するため、上記の文によれば、意味は次のようになります。誰のメソッドを呼び出すかを決めるのはBがあるので、a2.show(b)はBのshow(B obj)を呼び出し、結果は「BとB」になるはずだと言われていますが、なぜそれと違うのですか?前回の走行結果は?ここで、「ただし、ここで呼び出されるメソッドはスーパークラスで定義する必要があります」という文を無視すると、show(B obj)はクラスAに存在しますか?それは存在しません!では、この文はここでは当てはまらないのでしょうか?では、この文は間違っているのでしょうか?いいえ!実際、この文は次の文も暗示しています。継承チェーン内の呼び出しメソッドの優先順位に従って確認する必要があります。そのため、クラス A で show(A obj) が見つかります。同時に、B がこのメソッドを書き換えたため、クラス B のメソッドを呼び出します。それ以外の場合は、クラス A のメソッドを呼び出します。

したがって、多態性メカニズムに従う原則は次のように要約できます: スーパークラスのオブジェクト参照変数がサブクラス オブジェクトを参照する場合、参照変数の型ではなく、参照されるオブジェクトの型によって、どのメンバー メソッドが呼び出されるかが決まりますが、呼び出されるメソッドはスーパークラスで定義されている必要があります。つまり、メソッドはサブクラスによってオーバーライドされますが、継承チェーン内のメソッド呼び出しの優先順位に従ってメソッドを確認する必要があります。優先順位は次のとおりです。 this.show(O)、super.show (O)、this.show((super)O)、super.show((super)O)。

概要

上記がこの記事の全内容です。この記事の内容が皆さんの学習や仕事に少しでも役立つことを願っています。ご質問がある場合は、メッセージを残して連絡してください。

Java の 3 つの主要な機能のうちポリモーフィズムをさらに詳しく理解するには、PHP 中国語 Web サイトに注目してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。