This is the 9th day of my participation in the August Wen Challenge.More challenges in August

Today we continue to talk about Java polymorphism. Many beginners are stuck with polymorphism when learning Java by themselves. Polymorphism refers to the ability to have multiple different manifestations or forms of the same behavior.

Doesn’t it feel abstract and hard to understand… That’s ok, I’m going to take you through Java polymorphism and nail it down!!

Java polymorphism

Polymorphism is one of the three characteristics of object orientation. Its premise is that encapsulation forms independent bodies and there is an inheritance relationship between independent bodies, thus generating the mechanism of Polymorphism. Polymorphism is the ability to have many different manifestations or forms of the same behavior. In real life, for example, we press F1:

● If the help document for AS 3 is displayed in the Flash interface;

● If the current popup under Word is Word help;

● Windows Help and Support if it is currently popping up under Windows.

Polymorphism is when “the same behavior” occurs on “different objects” with different effects.

So how is polymorphism represented in Java?

Java allows two such syntax: Upcasting, which refers to a subtype being converted to a parent type, and Downcasting, which refers to a parent type being converted to a subtype, also known as casting. See the picture below:

There is a rule in the Java language that there must be an inheritance relationship between the two types, whether it is up or down. The compiler will report an error when the transition is up or down without inheritance.

Let’s look at some code:

public class Animal {
	public void move(){
		System.out.println("Animal move!");
	}
}
Copy the code
Public class extends Animal{// extends Animal public void move(){system.out.println (" Cat walk! ); } public void catchMouse(){system.out.println (" catchMouse! ); }}Copy the code
Public class extends Animal{public void move(){system.out.println (" Bird is flying! "); ); } // public void sing(){system.out.println (" The birds are singing!" ); }}Copy the code
Public class Test01 {public static void main(String[] args) {// Create Animal object Animal a = new Animal(); a.move(); // create Cat object Cat c = new Cat(); c.move(); // create the Bird object Bird b = new Bird(); b.move(); }}Copy the code

The running results are as follows:

Java also allows you to write code like this:

public class Test02 { public static void main(String[] args) { Animal a1 = new Cat(); a1.move(); Animal a2 = new Bird(); a2.move(); }}Copy the code

The running results are as follows:

The above procedures demonstrate polymorphism, polymorphism is “the same action (move)” on “different objects” will have different performance results.

Polymorphism exists in Java because Java allows a reference to a parent type to point to an object of a child type. Animal A2 = new Bird(), because Bird is an Animal.

Animal A1 = new Cat() or Animal A2 = new Bird() are parent type references to subtypes, which are Upcasting, or automatic type conversions.

Animal A1 = ****new**** Cat();a1.move(); :

A Java program consists of two stages: compile and run. The compiler must analyze the compile phase first, and then analyze the run phase. In the compile phase, the compiler only knows that a1 is of Animal type, so the compiler will look for the move() method in the animal.class bytecode. The move() method is found in animal.class bytecode, and the move() method is bound to an A1 reference.

Then the program starts to run and enters the run stage. During the run, the new object in the heap memory is Cat type, that is, when the real move moves, it is Cat Cat object that moves, so the move() method in Cat class will be automatically executed during the run. This process can be called “dynamic binding”. However, at any time, the “static binding” must be successful before the “dynamic binding” phase can be entered.

Take a look at the following code and the compilation result:

public class Test03 { public static void main(String[] args) { Animal a = new Cat(); a.catchMouse(); }}Copy the code

Compile result:

A Cat is a Cat that can catch mice.

That’s because “Animal A = ****new**** Cat();” At compile time, the compiler only knows that the datatype of a variable is Animal, which means it will only look for the catchMouse() method in animal.class bytecode. Same as the error message described above: method catchMouse() cannot be found in variable A of type Animal.

So, if I wanted the cat to catch the mouse, how would this code change?

Take a look at the following code:

Public class Test04 {public static void main(String[] args) {// Animal a = new Cat(); // Downward transition: to call the subclass object specific method Cat c = (Cat)a; c.catchMouse(); }}Copy the code

The running results are as follows:

The catchMouse() method cannot be invoked using a reference to the catchMouse() method, because this method is a unique behavior of the subclass Cat. Use cast to convert Animal a reference to Cat c (Cat c = (Cat)a;) Call the catchMouse() method with a C reference of type Cat.

From this example, you can see that only when accessing data unique to a subtype, a downward transformation is required first. In fact, this is where the downward transformation is used.

So what are the risks of downshifting? Take a look at the following code:

public class Test05 { public static void main(String[] args) { Animal a = new Bird(); Cat c = (Cat)a; }}Copy the code

Can the above code compile? The answer is yes, why?

That’s because the compiler only knows that a is Animal. Animal and Cat have an inheritance relationship, so it can go down. There are no syntax errors, so the compiler passes. But is it going to be a problem when it runs, because after all, the real object that the A reference points to is a bird? To see the results:

ClassCastException this is a common ClassCastException that occurs during a downcast operation when the type is incompatible. This exception is caused by the fact that the object a refers to at runtime is a bird. Then we have to convert a bird into a cat, which obviously doesn’t make sense, because there’s no inheritance between birds and cats. To avoid such exceptions, it is recommended to do run-time type determination before downcasting, which requires learning an operator called instanceof.

The syntax of the instanceof operator looks like this:

(Reference the instanceof type)

The result of the instanceof operator is Boolean, which can be true or false. If (c instanceof Cat) is true, then c references to objects of type Cat at runtime. If the result is false, the object referenced at runtime is not of type Cat. With the instanceof operator, a downward transition could be written like this:

public class Test05 { public static void main(String[] args) { Animal a = new Bird(); if(a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); }}}Copy the code

If (a instanceof Cat) is false, then it will not be converted down. If (a instanceof Cat) is false, then it will not be converted down. There will be no ClassCastException.

In practical development, there is a default specification in Java that needs to be kept in mind: It is good programming practice to use instanceof to make a judgment call before any downward transition. Like the following code:

public class Test05 { public static void main(String[] args) { Animal a = new Bird(); if(a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); }else if(a instanceof Bird){ Bird b = (Bird)a; b.sing(); }}}Copy the code

The running results are as follows:

Illustration: Judgment before downward transformation

Does everyone understand what polymorphism is at this point? In fact, the three necessary conditions for the existence of polymorphism are:

Low inheritance

● Method coverage

● A parent type reference points to an object of a child type

Polymorphism is clearly is dependent on the method of covering, polymorphism is because compilation phase of binding the superclass method, the program runs stage automatically call the subclass object method, method of if a subclass object has not been rewritten, this time it’s meaningless to create a subclass object, natural polymorphism would be meaningless, Polymorphism occurs only when a subclass overrides a method and calls the method on a subclass object. In fact, method covering mechanism and polymorphism mechanism are bound, who can not do without who, polymorphism can not do without method covering, method covering without polymorphism is meaningless.

Next, let’s look at the problem that has not been solved before: method coverage mainly refers to instance methods. Why not static methods?

public class OverrideTest { public static void main(String[] args) { Math.sum(); MathSubClass.sum(); }}Copy the code
public class Math{
	public static void sum(){
		System.out.println("Math's sum execute!");
	}
}
Copy the code
Public class MathSubClass extends Math{public static void sum(){system.out.println ("MathSubClass's  sum execute!" ); }}Copy the code

The running results are as follows:

Illustration: Try to override static methods

We see that overwriting appears to have occurred, and that the sum method of subclass MathSubClass is actually called when the program is running, but is this overwriting meaningful? In fact, we have already mentioned in the previous course that method overlays and polymorphisms make sense together. Let’s see if this “overlays” can achieve the effect of “polymorphisms”, see the code:

public class OverrideTest { public static void main(String[] args) { Math m = new MathSubClass(); m.sum(); m = null; m.sum(); }}Copy the code

The running results are as follows:

MathSubClass() : ****new**** MathSubClass(); sum() : m = null; m = null; This shows that the execution of static methods is fundamentally independent of the object. Since it is independent of the object, it means that it is independent of polymorphism. Since it is independent of polymorphism, that is to say, the “coverage” of static methods is meaningless, so we usually do not talk about the coverage of static methods.