Home >Java >javaTutorial >In-depth understanding of polymorphism among the three major features of Java

In-depth understanding of polymorphism among the three major features of Java

高洛峰
高洛峰Original
2017-01-19 14:12:031328browse

Three Major Characteristics of Java

Object-oriented programming has three major characteristics: encapsulation, inheritance, and polymorphism.

Encapsulation hides the internal implementation mechanism of the class. It can change the internal structure of the class without affecting its use, and also protects the data. Only its internal details are hidden from the outside world, and only its access methods are exposed to the outside world.

Inheritance is to reuse parent class code. Inheritance can be used if there is an IS-A relationship between two classes. , and inheritance also pave the way for the realization of polymorphism. So what is polymorphism? What is the implementation mechanism of polymorphism? Please watch me reveal it to you one by one:

The so-called polymorphism means that the specific type pointed to by the reference variable defined in the program and the method call issued through the reference variable are not determined during programming, but It is determined during the running of the program, that is, which class instance object a reference variable will point to, and the method call issued by the reference variable is a method implemented in which class, which must be determined during the running of the program. Because the specific class is determined only when the program is running, the reference variable can be bound to various class implementations without modifying the source program code, causing the specific method called by the reference to change accordingly, that is, it does not need to be modified. The program code can change the specific code bound to the program when it is running, allowing the program to select multiple running states. This is polymorphism.

For example, you are a wine god and have a special liking for wine. One day I went home and found several glasses on the table filled with white wine. It was impossible to tell what kind of wine it was from the outside. Only after drinking it could we guess what kind of wine it was. When you drink it, it is Jiannanchun, when you drink it again, it is Wuliangye, and when you drink it again, it is Jiuguijiu... Here we can describe it as follows:

wine a = Jiannanchun

wine b = Wuliangye

wine c = Jiuguijiu

What is shown here is polymorphism. Jiannanchun, Wuliangye, and Jiuguijiu are all subclasses of wine. We can reference different subclasses just through the parent class of wine. This is polymorphism - we only know the specific point of the reference variable when running. instance object.

It is true that to understand polymorphism we must understand what "upward transformation" is. In inheritance, we briefly introduced upward transformation. Here’s a brief explanation: In the drinking example above, Wine (Win) is the parent class, and Jiannanchun (JNC), Wuliangye (WLY), and Jiuguijiu (JGJ) are subclasses. We define the following code:

JNC a = new JNC();

It is very easy for us to understand this code, which is nothing more than instantiating a Jian Nanchun object! But what about this?

Wine a = new JNC();

We understand here that a Wine type a is defined here, which points to a JNC object instance. Since JNC inherits from Wine, JNC can be automatically transformed into Wine, so a can point to the JNC instance object. There is a very big benefit in doing this. In inheritance, we know that the subclass is an extension of the parent class. It can provide more powerful functions than the parent class. If we define a parent class reference type that points to the subclass, then it In addition to being able to reference the common features of the parent class, you can also use the powerful functions of the subclass.

But there are some shortcomings in upward transformation, that is, it will definitely lead to the loss of some methods and properties, causing us to be unable to obtain them. Therefore, the reference of the parent class type can call all properties and methods defined in the parent class, but it is beyond the reach of methods and properties that only exist in the subclass---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...

From the running results of the program, we found that a.fun1() first runs fun1() in the parent class Wine. Then it runs fun2() in the subclass JNC.

Analysis: In this program, the subclass JNC overloads the method fun1() of the parent class Wine and rewrites fun2(), and the overloaded fun1(String a) and fun1() are not the same method, since there is no such method in the parent class, the method will be lost after upward transformation, so the Wine type reference that executes JNC cannot reference the fun1 (String a) method. The subclass JNC rewrites fun2(), so the Wine reference pointing to JNC will call the fun2() method in JNC.

So we can summarize polymorphism as follows:

The parent class reference pointing to the subclass is transformed upward, it can only access the methods and properties owned by the parent class, and for the subclass For a method that exists in the parent class but does not exist in the parent class, the reference cannot be used, even if the method is overloaded. If a subclass overrides some methods in the parent class, when calling these methods, it must use the methods defined in the subclass (dynamic connection, dynamic calling).

For object-oriented purposes, polymorphism is divided into compile-time polymorphism and run-time polymorphism. Among them, editing-time polymorphism is static and mainly refers to method overloading. It distinguishes different functions based on different parameter lists. After editing, it will become two different functions. There is no polymorphism at runtime. . Runtime polymorphism is dynamic, and it is achieved through dynamic binding, which is what we call polymorphism.

Polymorphic implementation

2.1 Implementation conditions

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

First we analyze 5, a2.show(c), a2 is a reference variable of type A, so this represents A, a2.show(c), it is not found in class A, so it goes to A Find (super) in the super class of )O is B and A, and this is also A. Show(A obj) is found in A. At the same time, since a2 is a reference of class B and class B overrides show(A obj), the subroutine will eventually be called. The show(A obj) method of class B, the result is B and A.

I can also confirm other answers by following the same method.

The method has been found, but we still have some doubts here. Let’s look at this sentence: When a superclass object reference variable refers to a subclass object, the type of the referenced object is determined rather than the type of the reference variable. Whose member method is called, but the called method must be defined in the super class, that is, a method overridden by the subclass. Let’s use an example to illustrate the meaning of this sentence: a2.show(b);

Here a2 is a reference variable, which is of type A. It refers to the B object, so according to the above sentence What this means is that B decides which method to call, so a2.show(b) should call show(B obj) in B, and the result should be "B and B", but why is it different from the previous one? Are there any differences in the running results? Here we ignore the following sentence "But the method called here must be defined in the super class", then does show(B obj) exist in class A? It doesn't exist! So this sentence doesn't apply here? So is this sentence wrong? No! In fact, this sentence also implies this sentence: it still needs to be confirmed according to the priority of the calling method in the inheritance chain. That's why it will find show(A obj) in class A. At the same time, because B has rewritten this method, it will call the method in class B, otherwise it will call the method in class A.

So the principle followed by the polymorphic mechanism can be summarized as follows: When a superclass object reference variable refers to a subclass object, the type of the referenced object rather than the type of the reference variable determines whose member method is called, but this is The method called must be defined in the superclass, that is to say, the method is overridden by the subclass, but it still needs to confirm the method according to the priority of the method call in the inheritance chain. The priority is: this.show(O) , super.show(O) , this.show((super)O) , super.show((super)O) .

Summary

The above is the entire content of this article. I hope the content of this article can bring some help to everyone's study or work. If you have any questions, you can leave a message to communicate.

For more in-depth understanding of polymorphism among the three major features of Java, please pay attention to the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn