[javaSE] Polymorphism of the three major characteristics of object-oriented programs

Table of contents

The concept of polymorphism

Polymorphic Realization Conditions

rewrite

Rules for Method Overriding

The difference between rewriting and overloading

Design Principles for Rewriting

Static binding:

Dynamic binding:

Upward transformation and downward transformation

Upward transformation

Grammar format:

scenes to be used

 Advantages and disadvantages

Downward transformation

instanceof keyword 

Advantages and disadvantages of polymorphism

Advantages of polymorphism

1. It can reduce the "cycling complexity" of the code and avoid using a lot of if - else

 2. Stronger scalability

Polymorphic defects:

Avoid calling overridden methods in constructors

in conclusion

Summarize


The concept of polymorphism

The concept of polymorphism: Generally speaking, it is a variety of forms. The specific point is to complete a certain behavior. When different objects are completed, different states will be produced.

For example, the printers in our real life

Another example is the cat and dog mentioned in the blogger's inheritance

 In general: the same thing, happening to different objects, will produce different results.

Polymorphic Realization Conditions

To achieve polymorphism in java, the following conditions must be met, all of which are indispensable

1. Must be under the inheritance system

2. The subclass must rewrite the method in the parent class

3. Call the rewritten method through the reference of the parent class

The embodiment of polymorphism is: when the code is running, when different types of objects are passed, the methods in the corresponding classes will be called.

Then let's show you a simple polymorphic implementation here. We use the cat and dog classes we used in inheritance

First we create a parent class Animal

public class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}

Then we subclass the Cat class. This involves rewriting, which will be discussed later, and the blogger here only demonstrates the effect

public class Cat extends Animal{
    public Cat(String name, int age){
        super(name, age);
    }
    @Override
    //重写
    public void eat(){
        System.out.println(name+"吃鱼~~~");
    }
}

Create a subclass of the Dog class. Also this involves rewriting, which will be discussed later, here the blogger only demonstrates the effect

public class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    }
    @Override
    //重写
    public void eat(){
        System.out.println(name+"吃骨头~~~");
    }
}

Then we create a Main class for implementation, which involves upward transformation, which will be discussed later, here only demonstrates the effect

public class Main {

    // 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
    // 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
    // 注意:此处的形参类型必须时父类类型才可以

    public static void eat(Animal a) {
        a.eat();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("元宝", 2);
        Dog dog = new Dog("小七", 1);
        eat(cat);
        eat(dog);

    }
}

Next, let's look at the effect

This is a simple polymorphic

In the above code, the code of the Main class is written by the caller of our class, while the code of other classes is written by the implementor of the class

When the caller of the class is writing the eat method, the parameter type is Animal (parent class), and at this time, he does not know inside the method, and does not pay attention to which type (which subclass) the current a reference points to Instance. At this time, a reference to call eat method may have a variety of different performances (related to the instance referenced by a), this behavior is called polymorphism

 Next, I will answer the new terms designed above one by one.

rewrite

Rewrite (override): Also known as coverage. Rewriting is the subclass rewriting the implementation process of the parent class's non-static, non-private modification, non-final modification, non-constructive method, etc. , and the return value and formal parameters cannot be changed . That is, the shell remains unchanged, but the core is rewritten! The advantage of rewriting is that subclasses can define their own specific behavior according to their needs. That is to say, the subclass can implement the method of the parent class as needed.

Rules for Method Overriding

1. When a subclass rewrites the method of the parent class, it must generally be consistent with the prototype of the parent class method: the return value type method name (parameter list) must be completely consistent

2. The return value type of the rewritten method can be different, but it must have a parent-child relationship

3. The access rights cannot be lower than the access rights of the overridden methods in the parent class. For example: if the parent class method is modified by public, the method rewritten in the subclass cannot be declared as protected

4. The methods and construction methods of the parent class modified by static and private cannot be rewritten.

5. The rewritten method can be explicitly specified using the @Override annotation. With this annotation, it can help us to perform some legality checks. For example, if you accidentally misspelled the method name (such as aet), then compile at this time The compiler will find that there is no aet method in the parent class, and it will compile and report an error, indicating that it cannot be rewritten.

The difference between rewriting and overloading

point of difference override overload
parameter list must not be modified must be modified
return type It must not be modified [unless it can form a parent-child relationship] can be modified
access qualifier Must not be more restrictive (restrictions can be lowered) can be modified

That is: method overloading is a polymorphic expression of a class, and method rewriting is a polymorphic expression of a subclass and a parent class

Design Principles for Rewriting

For classes that have already been put into use, try not to modify them. The best way is: redefine a new class to reuse the common content and add or change new content.

For example: a few years ago, mobile phones could only make calls and send text messages, and the caller ID could only display the number, but today's mobile phones can not only display the number, but also display the avatar, region, etc. when the caller ID is displayed. During this process, we should not modify the original old class, because the original class may still be used by users . The correct way is: create a new mobile phone class, and rewrite the caller ID method. , so that we have reached our current needs.

Static binding:

Also known as early binding (early binding), that is, at compile time, the specific method to call is determined according to the actual parameter type passed by the user. Typical representative function overloading.

Dynamic binding:

Also known as late binding (late binding), that is, at compile time, the behavior of the method cannot be determined, and it is necessary to wait until the program is running to determine the method of the class to call

Upward transformation and downward transformation

Upward transformation

Upward transformation:

In fact, it is to create a subclass object and use it as a parent class object.

Grammar format:

parent class type object name = new subclass type()

For example 

Animal animal = new Cat("元宝",2);

 animal is the parent class type, but it can refer to a subclass object, because it is a conversion from a small range to a large range

scenes to be used

1. Direct assignment
2. Method passing parameters
3. Method return

public class TestAnimal {
    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a){
        a.eat();
    } 
    // 3. 作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("狗".equals(var) ){
            return new Dog("狗狗",1);
        }else if("猫" .equals(var)){
            return new Cat("猫猫", 1);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
        Dog dog = new Dog("小七", 1);
        eatFood(cat);
        eatFood(dog);
        Animal animal = buyAnimal("狗");
        animal.eat();
        animal = buyAnimal("猫");
        animal.eat();
    }
}

 Advantages and disadvantages

Advantages of upward transformation: make code implementation simpler and more flexible.

Defects of upward transformation: You cannot call methods specific to subclasses.

Downward transformation

Use a subclass object as a parent class method after upward transformation, and you can no longer call the method of the subclass, but sometimes you may need to call the unique method of the subclass. At this time: just restore the parent class reference to the subclass object , which is a downcast.


The example is as follows

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
// 编译失败,编译时编译器将animal当成Animal对象处理
// 而Animal类中没有bark方法,因此编译失败
// animal.bark();//bark()为子类特有的
// 向上转型
// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
// animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.bark();
    }
}

instanceof keyword 

Downcasting is rarely used, and it is not safe. If the conversion fails, an exception will be thrown at runtime. In order to improve the safety of downcasting in Java, instanceof is introduced. If the expression is true, it can be safely converted.

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }else if (animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

If you want to know more about the instanceof keyword, please click the link below

Chapter 15. Expressions (oracle.com)https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.20.2

Advantages and disadvantages of polymorphism

For example with the following code

class Shape {
    //属性....
    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

Advantages of polymorphism

1. It can reduce the "cycling complexity" of the code and avoid using a lot of if - else

What is "Cyclomatic Complexity"?
Cyclomatic complexity is a way to describe the complexity of a piece of code. If a piece of code is flat and straightforward, it is relatively simple and easy to understand. If there are many conditional branches or loop statements, it is considered easier to understand Complex. Therefore, we can simply and roughly calculate the number of conditional statements and loop statements in a piece of code. This number is called "cycling complexity". If the cyclomatic complexity of a method is too high, refactoring needs to be considered. Different companies have different specifications for the cyclomatic complexity of the code. Generally, it will not exceed 10

For example, we now need to print multiple shapes in the above code. If it is not based on polymorphism, the implementation code is as follows

public class TestMain {
    public static void drawShapes() {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Flower flower = new Flower();
        String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
        for (String shape : shapes) {
            if (shape.equals("cycle")) {
                cycle.draw();
            } else if (shape.equals("rect")) {
                rect.draw();
            } else if (shape.equals("flower")) {
                flower.draw();
            }
        }
    }

If you use polymorphism, you don't have to write so many if - else branch statements, and the code is simpler.

    public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
                new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();//for-eich进行遍历
        }
    }

 2. Stronger scalability

If you want to add a new shape, the code modification cost is relatively low by using polymorphism.

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("△");
    }
}

For the caller of the class (drawShapes method), just create an instance of a new class, and the cost of modification is very low.

For the case where polymorphism is not used, the if - else in drawShapes must be modified to a certain extent, and the modification cost is higher.

Polymorphic defects:

Code runs less efficiently.
1. There is no polymorphism in attributes. When both the parent class and the subclass have attributes with the same name, only the member attributes of the parent class can be referenced through the reference of the parent class.

2. The constructor has no polymorphism

Avoid calling overridden methods in constructors

A piece of code with pitfalls. We create two classes, B is the parent class, D is the subclass. The func method is rewritten in D. And func is called in the construction method of B, the code is as follows

class B {
    public B() {
// do nothing
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}

The execution result is

Parse:

When the D object is constructed, the constructor of B will be called.

The func method is called in the construction method of B, and the dynamic binding will be triggered at this time, and the func in D will be called

At this time, the D object itself has not been constructed yet, and num is in an uninitialized state, with a value of 0. If it has polymorphism, the value of num should be 1.

So in the constructor, try to avoid using instance methods, except final and private methods.

in conclusion

"Use as simple a way as possible to make the object into a workable state", try not to call methods in the constructor (if this method is overridden by a subclass, dynamic binding will be triggered, but the subclass object has not yet been constructed), There may be some hidden but extremely difficult to find problems.

Summarize

This is the end of the explanation of "Three Characteristics of Object-Oriented Programs: Polymorphism". You are welcome to leave a message for exchange and criticism. If the article is helpful to you or you think the author's writing is not bad, you can click to follow, like, and bookmark for support.


 

Guess you like

Origin blog.csdn.net/m0_71731682/article/details/132040074