Maison > Java > javaDidacticiel > Compréhension approfondie du polymorphisme parmi les trois fonctionnalités majeures de Java

Compréhension approfondie du polymorphisme parmi les trois fonctionnalités majeures de Java

高洛峰
Libérer: 2017-01-19 14:12:03
original
1324 Les gens l'ont consulté

Trois fonctionnalités majeures de Java

La programmation orientée objet possède trois fonctionnalités majeures : l'encapsulation, l'héritage et le polymorphisme.

L'encapsulation masque le mécanisme d'implémentation interne de la classe. Elle peut modifier la structure interne de la classe sans affecter son utilisation, et protège également les données. Seuls ses détails internes sont cachés au monde extérieur, et seules ses méthodes d'accès sont exposées au monde extérieur.

L'héritage consiste à réutiliser le code de la classe parent. L'héritage peut être utilisé s'il existe une relation IS-A entre deux classes. , et l'héritage ouvrent également la voie à la réalisation du polymorphisme. Alors, qu’est-ce que le polymorphisme ? Quel est le mécanisme de mise en œuvre du polymorphisme ? Veuillez me regarder vous le révéler un par un :

Le soi-disant polymorphisme signifie que le type spécifique pointé par la variable de référence définie dans le programme et l'appel de méthode émis via la variable de référence ne sont pas déterminés pendant programmation, mais Il est déterminé lors de l'exécution du programme, c'est-à-dire vers quel objet d'instance de classe une variable de référence pointera, et l'appel de méthode émis par la variable de référence est une méthode implémentée dans quelle classe, qui doit être déterminée lors de la déroulement du programme. Étant donné que la classe spécifique n'est déterminée que lorsque le programme est en cours d'exécution, la variable de référence peut être liée à diverses implémentations de classe sans modifier le code du programme source, ce qui entraîne la modification en conséquence de la méthode spécifique appelée par la référence, c'est-à-dire qu'elle n'a pas besoin de être modifié. Le code du programme peut changer le code spécifique lié au programme lors de son exécution, permettant au programme de sélectionner plusieurs états d'exécution. Il s'agit du polymorphisme.

Par exemple, vous êtes un dieu du vin et avez un goût particulier pour le vin. Un jour, je suis rentré chez moi et j'ai trouvé plusieurs verres sur la table remplis de vin blanc. Il était impossible de dire de l'extérieur de quel type de vin il s'agissait. Ce n'est qu'après l'avoir bu que nous avons pu deviner de quel type de vin il s'agissait. Quand tu le bois, c'est Jiannanchun, quand tu le bois encore, c'est Wuliangye, et quand tu le bois encore, c'est Jiuguijiu... Ici on peut le décrire ainsi :

Vin a = Jiannanchun

Vin b = Wuliangye

vin c = Jiuguijiu

Ce qui est représenté ici, c'est le polymorphisme. Jiannanchun, Wuliangye et Jiuguijiu sont tous des sous-classes de wine. Nous pouvons référencer différentes sous-classes uniquement via la classe parent de wine. Nous ne connaissons le point spécifique de la variable de référence que lors de l'exécution.

Il est vrai que pour comprendre le polymorphisme il faut comprendre ce qu'est la « transformation vers le haut ». En matière d'héritage, nous avons brièvement introduit la transformation vers le haut. Voici une brève explication : dans l'exemple de consommation ci-dessus, Wine (Win) est la classe parent et Jiannanchun (JNC), Wuliangye (WLY) et Jiuguijiu (JGJ) sont des sous-classes. Nous définissons le code suivant :

JNC a = new JNC();

Il nous est très simple de comprendre ce code, qui n'est rien d'autre que l'instanciation d'un objet Jian Nanchun ! Mais qu'en est-il de cela ?

Wine a = new JNC();

Ici, nous comprenons cela, un type Wine a est défini ici, qui pointe vers une instance d'objet JNC. Puisque JNC hérite de Wine, JNC peut être automatiquement transformé en Wine, donc a peut pointer vers l'objet instance JNC. Cela présente un très grand avantage. En héritage, nous savons que la sous-classe est une extension de la classe parent. Elle peut fournir des fonctions plus puissantes que la classe parent si nous définissons un type de référence de classe parent qui pointe vers la sous-classe. , alors en plus de pouvoir référencer les fonctionnalités communes de la classe parent, vous pouvez également utiliser les fonctions puissantes de la sous-classe.

Mais la transformation ascendante présente certaines lacunes, c'est-à-dire qu'elle entraînera certainement la perte de certaines méthodes et propriétés, nous rendant incapables de les obtenir. Par conséquent, la référence du type de classe parent peut appeler toutes les propriétés et méthodes définies dans la classe parent, mais elle est hors de portée des méthodes et propriétés qui n'existent que dans la sous-classe --- 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...
Copier après la connexion

D'après les résultats d'exécution du programme, nous avons constaté que a.fun1() exécute d'abord fun1() dans la classe parent Wine, puis exécute fun2() dans la sous-classe JNC.

Analyse : Dans ce programme, la sous-classe JNC surcharge la méthode fun1() de la classe parent Wine et réécrit fun2(), et les surchargées fun1(String a) et fun1() ne sont pas la même méthode, comme il n'existe pas de méthode de ce type dans la classe parent, la méthode sera perdue après une transformation vers le haut, donc la référence de type Wine qui exécute JNC ne peut pas faire référence à la méthode fun1 (String a). La sous-classe JNC réécrit fun2(), donc la référence Wine pointant vers JNC appellera la méthode fun2() dans JNC.

Nous pouvons donc résumer le polymorphisme comme suit :

La référence de la classe parent pointant vers la sous-classe est transformée vers le haut, et elle ne peut accéder qu'aux méthodes et propriétés appartenant à la classe parent, tandis que pour la sous-classe Si une méthode existe dans la classe parent mais n'existe pas dans la classe parent, la référence ne peut pas être utilisée, même si la méthode est surchargée. Si une sous-classe remplace certaines méthodes de la classe parent, lors de l'appel de ces méthodes, elle doit utiliser les méthodes définies dans la sous-classe (connexion dynamique, appel dynamique).

À des fins orientées objet uniquement, le polymorphisme est divisé en polymorphisme au moment de la compilation et en polymorphisme au moment de l'exécution. Parmi eux, le polymorphisme au moment de l'édition est statique et fait principalement référence à la surcharge de méthode. Il distingue différentes fonctions en fonction de différentes listes de paramètres. Après l'édition, il n'y a pas de polymorphisme au moment de l'exécution. Le polymorphisme d'exécution est dynamique et est obtenu grâce à une liaison dynamique, ce que nous appelons le polymorphisme.

Implémentation polymorphe

2.1 Conditions d'implémentation

在刚刚开始就提到了继承在为多态的实现做了准备。子类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
-------------------------------
Copier après la connexion

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

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

Object o = new JGJ();
 
  System.out.println(o.toString());
Copier après la connexion

输出的结果是Wine : JGJ。

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

但是注意如果这样写:

Object o = new Wine();
 
System.out.println(o.toString());
Copier après la connexion

输出的结果应该是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)); 
 }
}
Copier après la connexion

运行结果:

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
Copier après la connexion

在这里看结果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存在如下关系。

Nous analysons d'abord 5, a2.show(c), a2 est une variable de référence de type A, donc cela représente A, a2.show(c), elle ne se trouve pas dans la classe A, donc elle va à A Find (super) dans la super classe de )O est B et A, et c'est aussi A. Show(A obj) se trouve dans A. En même temps, puisque a2 est une référence de classe B et que la classe B remplace show( A obj), le sous-programme sera finalement appelé La méthode show(A obj) de classe B, le résultat est B et A.

Je peux également confirmer d'autres réponses en suivant la même méthode.

La méthode a été trouvée mais nous avons encore un petit doute ici. Regardons cette phrase : Lorsqu'une variable de référence d'objet de superclasse fait référence à un objet de sous-classe, le type de l'objet référencé est déterminé plutôt que le type. de la variable de référence. Dont la méthode membre est appelée, mais la méthode appelée doit être définie dans la super classe, c'est-à-dire une méthode remplacée par la sous-classe. Utilisons un exemple pour illustrer le sens de cette phrase : a2.show(b);

Ici a2 est une variable de référence, qui est de type A. Elle fait référence à l'objet B, donc selon ce qui précède phrase Cela signifie que B décide quelle méthode appeler, donc a2.show(b) devrait appeler show(B obj) dans B, et le résultat devrait être "B et B", mais pourquoi est-il différent du précédent ? Y a-t-il des différences dans les résultats d'exécution ? Ici, nous ignorons la phrase suivante "Mais la méthode appelée ici doit être définie dans la super classe", alors show(B obj) existe-t-il dans la classe A ? Cela n'existe pas ! Donc cette phrase ne s'applique pas ici ? Alors cette phrase est-elle fausse ? Non! En fait, cette phrase implique aussi cette phrase : elle reste à confirmer selon la priorité de la méthode appelante dans la chaîne d'héritage. C'est pourquoi il trouvera show(A obj) dans la classe A. En même temps, comme B a réécrit cette méthode, il appellera la méthode de la classe B, sinon il appellera la méthode de la classe A.

Ainsi, le principe suivi par le mécanisme de polymorphisme peut être résumé comme suit : lorsqu'une variable de référence d'objet de superclasse fait référence à un objet de sous-classe, le type de l'objet référencé plutôt que le type de la variable de référence détermine dont la méthode membre est appelée, mais c'est La méthode appelée doit être définie dans la superclasse, c'est-à-dire que la méthode est remplacée par la sous-classe, mais elle doit quand même confirmer la méthode en fonction de la priorité de l'appel de méthode dans la chaîne d'héritage. La priorité est : this.show(O) , super.show(O) , this.show((super)O) , super.show((super)O) .

Résumé

Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article pourra être utile aux études ou au travail de chacun. Si vous avez des questions, vous pouvez partir. un message à communiquer.

Pour une compréhension plus approfondie du polymorphisme parmi les trois fonctionnalités majeures de Java, veuillez prêter attention au site Web PHP chinois !

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal