Heim  >  Artikel  >  Java  >  Vertiefendes Verständnis des Polymorphismus als eines der drei Hauptmerkmale von Java

Vertiefendes Verständnis des Polymorphismus als eines der drei Hauptmerkmale von Java

高洛峰
高洛峰Original
2017-01-19 14:12:031183Durchsuche

Drei Hauptmerkmale von Java

Objektorientierte Programmierung weist drei Hauptmerkmale auf: Kapselung, Vererbung und Polymorphismus.

Die Kapselung verbirgt den internen Implementierungsmechanismus der Klasse. Sie kann die interne Struktur der Klasse ändern, ohne ihre Verwendung zu beeinträchtigen, und schützt außerdem die Daten. Nur seine internen Details bleiben der Außenwelt verborgen und nur seine Zugriffsmethoden sind der Außenwelt zugänglich.

Vererbung besteht darin, den Code der übergeordneten Klasse wiederzuverwenden. Vererbung kann verwendet werden, wenn zwischen zwei Klassen eine IS-A-Beziehung besteht. und Vererbung ebnen auch den Weg für die Verwirklichung von Polymorphismus. Was ist also Polymorphismus? Was ist der Implementierungsmechanismus des Polymorphismus? Bitte sehen Sie zu, wie ich es Ihnen nacheinander enthülle:

Der sogenannte Polymorphismus bedeutet, dass der spezifische Typ, auf den die im Programm definierte Referenzvariable zeigt, und der über die Referenzvariable ausgegebene Methodenaufruf dabei nicht bestimmt werden Programmieren, aber Es wird während der Ausführung des Programms bestimmt, das heißt, auf welches Klasseninstanzobjekt eine Referenzvariable zeigt, und der von der Referenzvariablen ausgegebene Methodenaufruf ist eine Methode, die in welcher Klasse implementiert ist, was während der Ausführung bestimmt werden muss Ausführung des Programms. Da die spezifische Klasse nur bestimmt wird, wenn das Programm ausgeführt wird, kann die Referenzvariable an verschiedene Klassenimplementierungen gebunden werden, ohne den Quellprogrammcode zu ändern, wodurch sich die durch die Referenz aufgerufene spezifische Methode entsprechend ändert, das heißt, dies ist nicht erforderlich Der Programmcode kann den an das Programm gebundenen spezifischen Code während der Ausführung ändern, wodurch das Programm mehrere Ausführungszustände auswählen kann. Dies ist Polymorphismus.

Du bist zum Beispiel ein Weingott und hast eine besondere Vorliebe für Wein. Eines Tages ging ich nach Hause und fand auf dem Tisch mehrere Gläser voller Weißwein. Von außen konnte man nicht erkennen, um welche Art von Wein es sich handelte. Wenn man ihn trinkt, ist es Jiannanchun, wenn man ihn erneut trinkt, ist es Wuliangye, und wenn man ihn erneut trinkt, ist es Jiuguijiu... Hier können wir es wie folgt beschreiben:

Wein a = Jiannanchun

Wein b = Wuliangye

Wein c = Jiuguijiu

Was hier dargestellt wird, ist Polymorphismus. Jiannanchun, Wuliangye und Jiuguijiu sind alle Unterklassen von Wine. Dies ist Polymorphismus – wir kennen nur den spezifischen Punkt des Referenzinstanzobjekts.

Es ist wahr, dass wir, um Polymorphismus zu verstehen, verstehen müssen, was „Aufwärtstransformation“ ist. Bei der Vererbung haben wir kurz die Aufwärtstransformation eingeführt: Im obigen Trinkbeispiel ist Wine (Win) die übergeordnete Klasse und Jiannanchun (JNC), Wuliangye (WLY) und Jiuguijiu (JGJ) sind Unterklassen. Wir definieren den folgenden Code:

JNC a = new JNC();

Es ist für uns sehr einfach, diesen Code zu verstehen, der nichts anderes ist als die Instanziierung eines Jian Nanchun-Objekts! Aber was ist damit?

Wine a = new JNC();

Hier verstehen wir das, hier ist ein Wine-Typ a definiert, der auf eine JNC-Objektinstanz zeigt. Da JNC von Wine erbt, kann JNC automatisch in Wine umgewandelt werden, sodass a auf das JNC-Instanzobjekt verweisen kann. Dies hat einen großen Vorteil: Wir wissen, dass die Unterklasse eine Erweiterung der übergeordneten Klasse ist. Sie kann leistungsfähigere Funktionen bereitstellen als die übergeordnete Klasse, wenn wir einen Referenztyp für die übergeordnete Klasse definieren Dann können Sie nicht nur auf die gemeinsamen Funktionen der übergeordneten Klasse verweisen, sondern auch die leistungsstarken Funktionen der Unterklasse verwenden.

Aber es gibt einige Mängel bei der Aufwärtstransformation, das heißt, sie führt definitiv zum Verlust einiger Methoden und Eigenschaften, sodass wir sie nicht mehr erhalten können. Daher kann die Referenz des übergeordneten Klassentyps alle in der übergeordneten Klasse definierten Eigenschaften und Methoden aufrufen, liegt jedoch außerhalb der Reichweite von Methoden und Eigenschaften, die nur in der Unterklasse vorhanden sind ---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...

Aus den Laufergebnissen des Programms haben wir herausgefunden, dass a.fun1() zuerst fun1() in der übergeordneten Klasse Wine ausführt. Dann führt es fun2() in der Unterklasse JNC aus.

Analyse: In diesem Programm überlädt die Unterklasse JNC die Methode fun1 () der übergeordneten Klasse Wine und schreibt fun2 () neu, und die überladenen Methoden fun1 (String a) und fun1 () sind nicht dieselbe Methode. Da es in der übergeordneten Klasse keine solche Methode gibt, geht die Methode nach der Aufwärtstransformation verloren, sodass die Wine-Typreferenz, die JNC ausführt, nicht auf die Methode fun1 (String a) verweisen kann. Die Unterklasse JNC schreibt fun2() neu, sodass die auf JNC verweisende Wine-Referenz die Methode fun2() in JNC aufruft.

Wir können den Polymorphismus also wie folgt zusammenfassen:

Die Referenz der übergeordneten Klasse, die auf die Unterklasse zeigt, wird nach oben transformiert und kann nur auf die Methoden und Eigenschaften zugreifen, die der übergeordneten Klasse gehören, während für die Unterklasse Für eine Methode, die in der übergeordneten Klasse vorhanden ist, aber nicht in der übergeordneten Klasse vorhanden ist, kann die Referenz nicht verwendet werden, selbst wenn die Methode überladen ist. Wenn eine Unterklasse einige Methoden in der übergeordneten Klasse überschreibt, muss sie beim Aufrufen dieser Methoden die in der Unterklasse definierten Methoden verwenden (dynamische Verbindung, dynamischer Aufruf).

Nur ​​für objektorientierte Zwecke wird Polymorphismus in Polymorphismus zur Kompilierungszeit und Polymorphismus zur Laufzeit unterteilt. Unter ihnen ist der Polymorphismus zur Bearbeitungszeit statisch und bezieht sich hauptsächlich auf die Methodenüberladung. Nach der Bearbeitung gibt es keinen Polymorphismus. Laufzeitpolymorphismus ist dynamisch und wird durch dynamische Bindung erreicht, die wir Polymorphismus nennen.

Polymorphe Implementierung

2.1 Implementierungsbedingungen

在刚刚开始就提到了继承在为多态的实现做了准备。子类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存在如下关系。

Zuerst analysieren wir 5, a2.show(c). (super) in der Superklasse von )O ist B und A, und dies ist auch A. Show(A obj) wird in A gefunden. Gleichzeitig überschreibt Klasse B show(, da a2 eine Referenz von Klasse B ist und Klasse B überschreibt. A obj) wird schließlich die Methode show(A obj) der Klasse B aufgerufen, das Ergebnis ist B und A.

Mit der gleichen Methode kann ich auch andere Antworten bestätigen.

Die Methode wurde gefunden, aber wir haben hier immer noch ein wenig Zweifel. Schauen wir uns diesen Satz an: Wenn eine Objektreferenzvariable der Oberklasse auf ein Unterklassenobjekt verweist, wird der Typ des referenzierten Objekts und nicht der Typ bestimmt der Referenzvariablen, deren Mitgliedsmethode aufgerufen wird, aber die aufgerufene Methode muss in der Superklasse definiert sein, dh eine Methode, die von der Unterklasse überschrieben wird. Lassen Sie uns die Bedeutung dieses Satzes anhand eines Beispiels veranschaulichen: a2.show(b);

Hier ist a2 eine Referenzvariable, die vom Typ A ist. Sie bezieht sich auf das B-Objekt, also gemäß dem oben Gesagten Satz Dies bedeutet, dass B entscheidet, welche Methode aufgerufen werden soll. Daher sollte a2.show (b) show (B obj) in B aufrufen und das Ergebnis sollte „B und B“ sein. Warum unterscheidet es sich jedoch vom vorherigen? ? Gibt es Unterschiede in den Laufergebnissen? Hier ignorieren wir den folgenden Satz „Aber die hier aufgerufene Methode muss in der Superklasse definiert werden“. Existiert show(B obj) dann in Klasse A? Es existiert nicht! Also trifft dieser Satz hier nicht zu? Ist dieser Satz also falsch? NEIN! Tatsächlich impliziert dieser Satz auch diesen Satz: Er muss noch entsprechend der Priorität der aufrufenden Methode in der Vererbungskette bestätigt werden. Aus diesem Grund wird show(A obj) in Klasse A gefunden. Da B diese Methode neu geschrieben hat, ruft es gleichzeitig die Methode in Klasse B auf, andernfalls ruft es die Methode in Klasse A auf.

Das Prinzip des Polymorphismusmechanismus lässt sich also wie folgt zusammenfassen: Wenn eine Referenzvariable eines Oberklassenobjekts auf ein Unterklassenobjekt verweist, bestimmt der Typ des referenzierten Objekts und nicht der Typ der Referenzvariable, wessen Mitgliedsmethode ist wird aufgerufen, aber das ist Die aufgerufene Methode muss in der Oberklasse definiert werden, das heißt, die Methode wird von der Unterklasse überschrieben, sie muss die Methode jedoch noch entsprechend der Priorität des Methodenaufrufs in der Vererbungskette bestätigen. Die Priorität ist: this.show(O) , super.show(O) , this.show((super)O) , super.show((super)O) .

Zusammenfassung

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels für alle beim Lernen oder bei der Arbeit hilfreich sein kann eine Botschaft zum Mitteilen.

Für ein tieferes Verständnis des Polymorphismus unter den drei Hauptmerkmalen von Java schauen Sie sich bitte die chinesische PHP-Website an!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn