Polymorphism in Java

1, Introduction

There are three characteristics of object-oriented language: encapsulation, inheritance and polymorphism. Among these three characteristics, polymorphism is the most significant one. In a way, the core feature of an OOP language is polymorphism. Encapsulation inheritance appears in many ways to realize polymorphism.

Polymorphism can be divided into two types:

1. Compile time polymorphism (static polymorphism): the method to be called is known at compile time, such as overloading

2. Runtime polymorphism (dynamic polymorphism, also known as dynamic binding): you only know which method to call when running

Overload is a definition that we put forward separately, and what we often say polymorphism generally refers to runtime polymorphism, that is, we have to wait until the last run to determine the method to be called, which is related to the operation mechanism of Java virtual machine, so polymorphic methods are also called delay methods.

2, Implementation method of polymorphism

There are two ways to implement polymorphism:

1. Subclass inherits parent class: subclass inherits the parent class and rewrites the method of the parent class

2. Implement interface method: the class that implements the interface implements the interface method

In fact, the two methods have something in common: the methods that implement the parent class or interface, and the core of polymorphism is the different manifestations of these rewritten or implemented methods in subclasses.

Upward Transformation:

Another concept that we must know about polymorphic implementation methods: upward transformation. Let's take an example:

 1 class ceshi{
 2     public static void main(String[] args) {
 3         animal a1 = new animal();
 4         animal a2 = new Dog();
 5         animal a3 = new Cat();
 6         a1.speak();
 7         a2.speak();
 8         a3.speak();
 9     }
10 }
11 class animal{
12     public void speak() {
13         System.out.println("this is an animal");
14     }
15 }
16 
17 class Dog extends animal{
18     @Override
19     public void speak() {
20        System.out.println("this is a Dog");
21     }
22 }
23 class Cat extends animal{
24     @Override
25     public void speak() {
26         System.out.println("this is a Cat");
27     }
28 }

Here, we have a base class, animal. Dog and Cat inherit the animal class and Override its speak method. Here, we can call dog and Cat subclasses (derived classes) of animal. Note the three definition methods of mian function at this time. The first is normal new, which creates an animal object, and lines 4 and 5 are upward transformation. You can think about what the output is

this is an animal
this is a Dog
this is a Cat

A magical thing happened. Our reference variable clearly defines the animal class, but the actual method is the method corresponding to the subclass. This is related to the mechanism of the JVM. For details, see this article:

  https://www.cnblogs.com/kaleidoscope/p/9790766.html

This is the upward transformation we want to introduce. The category of the base class defined on the left actually implements the method of the subclass on the right. Upward transformation is such a feature

For:

  animal a2 = new Dog();

On the left is the compilation type. The compilation type is animal, which tells the compiler that I have defined a reference variable of annimal type, and I will point to an object

On the right is the running type, and its running type is Dog. This is the dynamic binding mechanism we mentioned above. We only find the specific implementation method when running. Now we find that the running type is Dog, so execute Dog.speak

Then summarize what happened:

1. Dog inherited animal

2. Dog overrides the speak method of animal

3. An upward transition occurs in the main function (that is, a reference to a base class points to an object of a subclass)

Under these conditions, polymorphism occurs. It is the polymorphism mechanism that makes the reference of a base class point to the object of a subclass that makes the compiler recognize it

 

Also, note that the second Dog overrides the speak method. What if the speak method is static? What happens?

If you think about the running results, you will fall into the thinking trap. Static methods cannot be rewritten. Static means that there is only one copy, and objects other than it are not allowed to be rewritten. Therefore, speak is a static method, and an error will be reported if you try to rewrite it.

 

Then think about another question: what if there is no rewriting method? See the following code:

 1 class ceshi{
 2     public static void main(String[] args) {
 3         animal a1 = new animal();
 4         animal a2 = new Dog();
 5         animal a3 = new Cat();
 6         a1.speak();
 7         a2.speak();
 8         a3.speak();
 9     }
10 }
11 class animal{
12   public void speak() {
13         System.out.println("this is an animal");
14     }
15 }
16 
17 class Dog extends animal{
18 
19 }
20 class Cat extends animal{
21 
22 }

We can see that we have deleted the speak method in the subclass, which is related to the inherited characteristics. What is the result?

this is an animal
this is an animal
this is an animal

What happens here is that our running type is Dog or Cat. When running, we first go to these two subclasses to find the speak method. If we find that we can't find it, we will find it up. If we find the parent class is animal, we will execute it directly. This upward process can go up indefinitely until we find or find the Object class.

 

Let's think about another problem. The problem we discussed earlier is that the subclass calls the method of rewriting the parent class. Can the reference variable after upward transformation call the method unique to the subclass? For example:

 1 class ceshi{
 2     public static void main(String[] args) {
 3         animal a2 = new Dog();
 4         animal a3 = new Cat();
 5         a2.eat();//error
 6         a3.eat();//error
 7     }
 8 }
 9 class animal{
10   public void speak() {
11         System.out.println("this is an animal");
12     }
13 }
14 
15 class Dog extends animal{
16     public void eat(){
17         System.out.println("Dog eat");
18     }
19 
20 }
21 class Cat extends animal{
22     public void eat(){
23         System.out.println("Cat eat");
24     }
25 }

We can see that we can't call the Dog and Cat class specific method eat with the upward transformation reference variable, that is, the fifth and sixth lines will report an error. Let's see the reason for the error:

 

  We were surprised to find that the compiler said it could not find the eat method in animal. As we said earlier, for a:

  animal a2 = new Dog(); 

For the reference variable a2 defined in this way, its compilation type is animal on the left and its running type is Dog on the right. From the above results, the compiler first determines our total methods, that is, the number of methods from the compilation type, that is, the compiler cannot find the unique methods in the running class.

Then we can draw a conclusion: the number (type) of methods that can be called by the upward transformed reference variable is determined by the compilation type (on the left). When it comes to the specific implementation, first look at whether the subclass is rewritten. If it is rewritten, the subclass will be called, and if not, it will be traced upward, that is, the implementation call sequence of the actual method starts from the running type on the right, and it will be called when it is found, If you don't find it, go up (parent class) to find this method.

 

 

This is the whole picture of upward transformation. May I ask? What's the point? I quote a paragraph from it. I really have to use it myself to experience it, as follows:

 

1. substitutability. Polymorphism is replaceable for existing code. For example, the polymorphic Circle class works equally with any other circular geometry, such as a torus.


2. extensibility. Polymorphic code is extensible. Adding new subclasses does not affect the polymorphism, inheritance, and operation of other features of existing classes. In fact, newly added subclasses are easier to obtain polymorphic functions. For example, on the basis of realizing the polymorphism of cone, semi cone and hemisphere, it is easy to add the polymorphism of sphere class.


3. Interface capability. Polymorphism is realized by superclass providing a common interface to subclasses through method signature, which is improved or covered by subclasses. As shown in Figure 8.3 As shown in. The superclass Shape in the figure specifies two interface methods that implement polymorphism, computerarea() and computeVolume(). Subclasses, such as Circle and Sphere, improve or cover these two interface methods in order to achieve polymorphism.


4. flexibility. It embodies flexible and diverse operation in application and improves the use efficiency.


5. simplicity. Polymorphism simplifies the coding and modification process of application software, especially when dealing with the operation and operation of a large number of objects.

 

3, Downward transformation

As mentioned above, one of the characteristics of upward transformation is that the types of methods that can be called by referencing variables are determined by the compiler. We cannot call methods unique to subclasses. When we really want to call methods unique to subclasses, we can use downward transformation, such as:

 1 class ceshi{
 2     public static void main(String[] args) {
 3         animal a2 = new Dog();
 4         animal a3 = new Cat();
 5         Dog a4 = (Dog) a2;//Downward transformation
 6         a4.eat();//call
 7         a2 = (animal)a4;//Back to animal
 8         a2.speak();
 9     }
10 }
11 class animal{
12   public void speak() {
13         System.out.println("this is an animal");
14     }
15 }
16 
17 class Dog extends animal{
18     public void speak() {
19         System.out.println("this is an Dog");
20     }
21     public void eat(){
22         System.out.println("Dog eat");
23     }
24 
25 }
26 class Cat extends animal{
27     public void eat(){
28         System.out.println("Cat eat");
29     }
30 }
result:
Dog eat
this is an Dog

 

By observing the fourth and fifth lines, we can find that we have changed the compilation type of a2 to Dog and assigned it to a4. a4 successfully called Dog's unique method eat(). After a4 called eat, it can also change the compilation type back to animal. In this way, a4 is a reference variable after upward transformation. The compilation type has changed to animal, and the operation type is still Dog.

It can be observed that when we use type conversion, we can only modify its compiled type, and the running type has been fixed as early as the definition (in essence, the address has been fixed).  

 

Keywords: Java

Added by simwiz on Fri, 03 Dec 2021 07:15:43 +0200