Detailed explanation of Java polymorphism (2)

Upward transformation and downward transformation

Upward transformation

Definition: Actually, 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 ()

Animal animal = new Cat("Yuan Bao", 2);

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

Features:

  1. Compile-time polymorphism: Parent class reference variables can refer to subclass objects, and the compiler will check whether the type of the reference variable is compatible with the type of the object at compile time.

  2. Runtime polymorphism: At runtime, the corresponding method is called according to the actual object type pointed to by the reference variable to realize the polymorphism of the method.

  3. Restrict method access: After upward transformation, only the methods declared in the parent class can be called, and the new methods of the subclass cannot be called directly.

Usage scenarios: 1. Direct assignment 2. Method passing parameters 3. Method return

Example 1: Direct assignment:

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 直接赋值
        animal.makeSound(); // 调用的是 Dog 类的方法
    }
}

Example 2: method parameter passing

class Shape {
    void draw() {
        System.out.println("draw a shape");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("draw a circle");
    }
}

class Triangle extends Shape {
    @Override
    void draw() {
        System.out.println("draw a triangle");
    }
}
public class Test {
    public static void shapeDrawing(Shape s) {
        s.draw();
    }

    public static void main(String[] args) {
        Shape s1 = new Circle();
        Shape s2 = new Triangle();

        shapeDrawing(s1);
        shapeDrawing(s2);//方法传参,传递不同子类对象
    }
}

Example 3: Make a return value

class Vehicle {
    String getType() {
        return "Vehicle";
    }
}

class Car extends Vehicle {
    @Override
    String getType() {
        return "Car";
    }
}

class Bike extends Vehicle {
    @Override
    String getType() {
        return "Bike";
    }
}

public class Test {
    public static Vehicle getType(String type){
        if(type.equals("car")){
            return new Car();
        } else if(type.equals("bike")) {
            return new Bike();
        } else {
            return new Vehicle();
        }
    }

    public static void main(String[] args) {
        Vehicle v1 = getType("car");
        Vehicle v2 = getType("bike");
        Vehicle v3 = getType("plane");

        System.out.println(v1.getType());
        System.out.println(v2.getType());//方法返回,返回的实际上可能是子类对象
        System.out.println(v3.getType());
    }
}

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

Disadvantages of upward transformation: cannot call subclass-specific methods

Downward transformation

Use a subclass object as a parent class method after upward transformation. You cannot 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.

class Animal1 {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog1 extends Animal1 {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }

    void fetch() {
        System.out.println("Dog fetch the ball");
    }
}

public class Test1 {
    public static void main(String[] args) {
        Animal1 animal1 = new Dog1();//向上转型

        //向下转型
        if(animal1 instanceof Dog1) {
            Dog1 dog = (Dog1)animal1;//实例类型检查和向下转型
            dog.makeSound();//调用子类的方法
            dog.fetch();//调用子类特有的方法
        }
    }
}

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 ( used to check whether an object is an instance of a specified class or its subclass ) is introduced. If the result of the expression is true, it can be safely converted.

Advantages and disadvantages of polymorphism

advantage

Suppose you have the following code:

class Shape {
    //属性。。。
    public void draw() {
        System.out.println("画图形");
    }
}

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

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

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

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

Cyclomatic Complexity: A way of describing how complex a piece of code is. If a piece of code is straightforward, it is relatively simple and easy to understand, but if there are many conditional branches or loop statements, it is considered to be more complicated to understand.

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, we need to consider refactoring

Different companies have different specifications for cyclomatic complexity, but generally it will not exceed 10

For example, what we need now is not just to print one shape, but multiple shapes. If it is not based on polymorphism, the implementation code is as follows:

public static void drawShapes() {
    Rect rect = new Rect();
    Circle circle = new Circle();
    Star star = new Star();
    String[] shapes = {"circle", "rect", "circle", "rect", "star"};
    for(String shape : shapes) {
        if(shape.equals("circle")){
            circle.draw();
        } else if(shape.equals("rect")) {
            rect.draw();
        } else if(shape.equals("star")) {
            star.draw()
        }
    }

}

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

Shape[] shapes = {new Circle(), new Rect(), new Circle(), new Rect(), new Star()};
for(Shape shape : shapes) {
    shape.draw();
}

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 (main function) of the class, just create an instance of a new class, and the cost of modification is very low.

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

shortcoming

Reduced code execution efficiency

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

 

Guess you like

Origin blog.csdn.net/asdssadddd/article/details/132400725