8. Polymorphism
In object-oriented programming language, there are three characteristics: encapsulation, inheritance and polymorphism.
Polymorphism separates two concepts: what to do and how to do. What to do corresponds to the interface and how to do corresponds to the implementation.
Polymorphism can improve the organization and readability of code, and create extensible programs.
A base class (parent class) has many different subclasses. When calling through upward transformation, you can call the methods of different subclasses according to different subclasses.
8.1 further discussion on upward transformation
The pointer of the base class (parent class) points to the object of the child class, which is called upward transformation.
class Father{ void speak(){ System.out.println("I'm a parent") } } class Son extends Father{ void speak(){ System.out.println("I'm a subclass") } } public class Test{ public static void main(String[] args){ Father f = new Son(); } } // Output results // I'm a subclass
The Son class accepts a reference from a Father class. The speak method in the above code happens to be a method of the Father class, so no error will be reported. And the output is the subclass method.
8.1.1 object type
class Father{ void speak() {System.out.println("I'm a parent");} } class Son extends Father{ void speak() {System.out.println("I'm a subclass");} void play() {System.out.println("I'm a subclass");} } public class TestString{ public static void main(String[] args){ Father f = new Son(); f.play();//report errors!!! } }
The above code will report an error because there is no play method in the Father class.
So the author asked us to forget the object type and that it is a Son class. We only need to know that it is a Father class, which can only execute the methods in the Father class.
8.2 turnaround
Father f = new Son(); f.speak();
How does the compiler know that Father f points to a Son object, not another object. In fact, the compiler does not know.
In order to understand this problem in depth, it is necessary to study the topic of binding.
8.2.1 method call binding
Associating a method call with a method body is called binding. That is, the method name is bound to the specific method to be executed.
The default binding method for procedural language in early binding.
Late binding means that the runtime binds according to the type of object. Late binding is also called dynamic binding or runtime binding.
In Java, all methods are late bound except static method and final method (private method belongs to final method).
8.2.2 produce correct behavior
Once you know the fact that all methods in Java are polymorphic through binding, you can write program code that only deals with base classes.
class Shape{ public void draw(){} } class Circle extends Shape{ public void draw(){ System.out.println("Circle"); } } public Square extends Shape{ public void draw(){ System.out.println("Square"); } } public class Test{ public static void main(String[] args){ Shape a = new Circle(); Shape b = new Square(); a.draw(); b.draw(); } } // Output results // Circle // Square
8.2.3 scalability
Due to the polymorphism mechanism, we can add as many new types to the system as we need without changing the draw() method.
8.2.4 defect: cannot "override" private method
For private methods of the base class (parent class), subclasses cannot be overridden by polymorphism.
public class Father{ private void f(){ System.out.println("Private f method"); } public static void main(String[] args){ Father t = new Son(); t.f(); } } class Son extends Father{ public void f(){ System.out.println("Public f method"); } } // Output results // Private f method
### 8.2.5 defects: domain and static method
Only common methods can be polymorphic
The properties of an object and the static methods of an object cannot be polymorphic
class Super{ public int field = 0; public static int f(){ return 0;} } class Sub extends Super{ public int field = 1; public static int f(){ return 1;} } public class Test{ public static void main(String[] args){ Super s = new Sub(); System.out.println(s.field); System.out.println(s.f()); } } // Output results // 0 // 0
8.3 constructors (constructors) and polymorphisms
The constructor is not polymorphic because it is actually a static method, but the static declaration is implicit.
However, it is still necessary to understand how polymorphism works at a complex level.
class Meal{ Meal(){ System.out.println("Meal()"); } } class Bread{ Bread(){ System.out.println("Bread()"); } } class Cheese{ Cheese(){ System.out.println("Cheese()"); } } class Lettuce{ Lettuce(){ System.out.println("Lettuce()"); } } class Lunch extends Meal{ Lunch(){ System.out.println("Lunch()"); } } class PortableLunch extends Lunch{ PortableLunch(){ System.out.println("PortableLunch()"); } } public class Sandwish extends PortableLunch{ private Bread b = new Bread(); private Cheese c = new Cheese(); private Lettuce l = new Lettuce(); public Sandwish(){System.out.println("Sandwich()");} public static void main(String[] args){ new Sandwich(); } } // Output results // Meal() // Lunch() // PortableLunch() // Bread() // Cheese() // Lettuce() // Sandwich()
As shown in the figure above, SandWich inherits PortableLunch, ProtableLunch inherits Lunch, and Lunch inherits Meal.
The execution sequence is as follows
(1) Call the parent class constructor, and the parent class calls the parent class again, recursing repeatedly
(2) Initializes the members of the current class
(3) Call the constructor of the current class
8.3.2 inheritance and liquidation
Create new classes through composition and inheritance without worrying about object cleanup. Subclasses are left to the garbage collector for processing.
If you have to clean up, you need to create a dispose() method for the class. You need to override the dispose() method of the base class (where the dispose is set by the author himself, or you can design your own name)
8.3.3 behavior of polymorphic methods inside the constructor
What happens if a polymorphic method is called in the constructor (dynamically bound method)?
Polymorphic methods usually determine the specific method body at runtime. If it is invoked in the construction method, it will be difficult to detect errors.
Although no error will be reported, it will cause the variable to set the initial value, resulting in unexpected errors.
class Glyph{ void draw(){ System.out.println("Glyph");} Glyph(){ System.out.println("Glyph before"); draw(); System.out.println("Glyph after"); } } class RoundGlyph extends Glyph{ private int radius = 1; RoundGlyph(int r){ radius = r; System.out.println("RoundGlyph, radius="+radius); } void draw(){ System.out.println("RoundGlyph, radius="+radius) } } public class Test{ public static void main(String[] args){ new RoundGlyph(5); } } // Output results // Glyph before // RoundGlyph, radius=0 // Glyph after // RoundGlyph, radius=5
It is found from the above code that radius is never set to 0, but the result is 0. There is an unexpected error, so do not use polymorphic methods in the constructor
8.4 covariant return type
A covariant return type is added to Java SE5, which represents a subclass type in which the overridden method in the exported class (subclass) can return the return type of the base class (parent class) method.
class Grain{ public String toString(){return "Grain";} } class Wheat extends Grain{ public String toString(){ return "Wheat"; } } class Mill{ Grain process(){ return new Grain(); } } class WheatMill extends Mill{ Wheat process(){ return new Wheat(); } //This is a polymorphic method!!!, Although the return values are different, there is an inheritance relationship } public class Test{ public static void main(String[] args){ Mill m = new Mill(); Grain g = m.process();// Return Grain type System.out.println(g); m = new WheatMill(); g = m.process(); // It is also possible to return the Wheat type. System.out.println(g); } } // Output results // Grain // Wheat
In versions before SE5, subclass overriding process must force the return of Grain instead of Wheat.
After SE5, this restriction was lifted.
8.5 design with inheritance
When considering inheritance or combination, please give priority to combination, because combination is more flexible.
Inheritance needs to know the exact type when compiling.
8.5.1 pure inheritance and extension
Creating inheritance structures in a pure way seems to be the best way. That is, only the methods already established in the base class can be overridden in the exported class.
Let the inherited subclass completely replace the base class (parent class), and the inherited subclass does not need any additional information, that is, no method other than the parent class needs to be created. Therefore, the essence of is-a is the best choice for inheritance.
8.5.2 downward transformation and runtime type identification
Because the upward transformation will lose the specific type information, it is necessary to obtain the specific type information through the downward transformation.
class Father{ public void f(){} public void g(){} } class Son{ public void f(){} public void g(){} public void h(){} } public class Test{ public static void main(String[] args){ Father f1 = new Son(); f1.f();//Normal execution f1.h();//report errors ((Son)f1).h(); //After casting to Son type, you can execute } }
8.6 summary
Polymorphism means different forms. The same interface inherited from the base class, as well as different forms and versions of dynamic binding methods using the interface.
Polymorphism is implemented on the basis of inheritance and data abstraction.