[Java SE] Inheritance and polymorphism

Table of contents

【1】Inheritance

【1.1】Why inheritance

【1.2】Inheritance concept

【1.3】Inherited syntax

【1.4】Parent class member access

【1.4.1】Accessing member variables of the parent class in a subclass

【1.4.2】Accessing member variables of the parent class in a subclass

【1.5】super keyword

【1.6】Subclass construction method

【1.7】super and this

【1.8】Let’s talk about initialization again

【1.9】protected keyword

【1.10】Inheritance method

【1.11】final keyword

【1.12】Inheritance and combination

【2】Polymorphism

【2.1】The concept of polymorphism

【2.2】Polymorphic implementation conditions

【2.3】Rewriting

【2.4】Upward transfer and downward transformation

【2.4.1】Upward transformation

【2.4.2】Downward transformation

【2.5】Advantages and disadvantages of polymorphism

【2.6】Avoid calling overridden methods in the constructor

Inheritance and polymorphism

【1】Inheritance

【1.1】Why inheritance

        Java uses classes to describe entities in the real world. The product object after instantiation of a class can be used to represent entities in the real world. However, the real world is complex and there may be some correlations between things. In designing the program, Yes, it needs to be considered.

[For example] : Dogs and cats, they are both animal species.

 

Using Java language to describe, you will design:

/** - 动物狗类
 */
class Dog {
    public String _name;
    public int _age;
    public float _weight;

    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void Bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物猫类
 */
class Cat {
    public String _name;
    public int _age;
    public float _weight;

    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void Bark() {
        System.out.println(_name + "喵喵喵~~~");
    }
}

By observing the above code, you will find that there is a lot of duplication in the cat and dog classes, as shown below:

 

        Can these commonalities be extracted? The concept of inheritance is proposed in object-oriented thinking, which is specially used to extract common features and achieve code reuse .

【1.2】Inheritance concept

        Inheritance mechanism: It is the most important means of object-oriented programming to make code reusable. It allows programmers to extend and add new functions while maintaining the characteristics of the original class . This generates a new class, which is called derivation . kind. Inheritance presents the hierarchical structure of object-oriented programming and reflects the cognitive process from simple to complex. The main problem that inheritance solves is: extraction of common features and realization of code reuse . For example: dogs and cats are both animals, then we can extract the common content and then use the idea of ​​inheritance to achieve sharing.

         

        In the above illustration, both Dog and Cat inherit the Animal class , where: the Animal class is called the parent class/base class or super class, and the Dog and Cat can be called the subclasses/derived classes of Animal. After inheritance, the subclasses can be duplicated. Using the members in the parent class, the subclass only needs to care about its own newly added members when implementing it. From the concept of inheritance, we can see that the biggest role of inheritance is to achieve code reuse, and to achieve polymorphism (discussed later).

【1.3】Inherited syntax

In Java, if you want to express the inheritance relationship between classes, you need to use the extends keyword, as follows:

修饰符 class 子类 extends 父类 {
	// ...
}

[Redesigned for dogs and cats]

 


/** - 动物类(父类)
 * 
 */
class Animal {
    public String _name;
    public int _age;
    public float _weight;
    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物狗类(子类)
 */
class Dog extends Animal {
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

/** - 动物猫类(子类)
 */
class Cat extends Animal {
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

【transfer】

public static void main(String[] args) {
    Dog dog = new Dog();
    dog._name = "旺财";
    dog._age = 2;
    dog._weight = 12;
    dog.eat();
    dog.sleep();
    dog.bark();
    dog.print();
    
    Cat cat = new Cat();
    cat._name = "小六";
    cat._age = 3;
    cat._weight = 20;
    cat.eat();
    cat.sleep();
    cat.bark();
    cat.print();
}

【Notice】

  1. The subclass will inherit the member variables or member methods in the parent class to the subclass.

  2. After the subclass inherits the parent class, it must add its own unique members to reflect the difference from the base class, otherwise there is no need to inherit.

【1.4】Parent class member access

【1.4.1】Accessing member variables of the parent class in a subclass

  1. There are no member variables with the same name in subclasses and parent classes.

class Base {
    int a;
    int b;
}

public class Derived extends Base {
    int c;
    public void method() {
        a = 10;     // 访问父类中的a
        b = 20;     // 访问父类中的b
        c = 30;     // 访问子类中的c
    }
}
  1. The member variables of the subclass and the parent class have the same name.

class Base {
    int a;
    int b;
}

public class Derived extends Base {
    int a;  // 与父类的成员a同名,但类型相同
    char b;  // 与父类的成员b同名,但类型不同

    int c;
    public void method() {
        a = 101;
        b = 102;
        c = 103;

        System.out.println(a);    // 访问父类继承的a,还是子类自己新增的a? (就近原则访问子类)
        System.out.println(b);    // 访问父类继承的b,还是子类自己新增的b?  (就近原则访问子类)
        System.out.println(c);    // 子类没有c,访问的肯定是从父类继承下来的c
        // System.out.println(d); // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
    }

    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.method();
    }
}

When accessing members in a subclass method or through a subclass object:

  • If the member variable to be accessed exists in a subclass, access your own member variable first.

  • If the member variable being accessed does not exist in the subclass, it will be accessed inherited from the parent class. If the parent class is not defined, a compilation error will be reported.

  • If the member variable being accessed has the same name as a member variable in the parent class, your own will be accessed first.

Member variable access follows the principle of proximity. If you have one, you have priority. If not, you will find it in the parent class. 

【1.4.2】Accessing member variables of the parent class in a subclass

  1. Member method names are different

class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
}
public class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodB(); // 访问子类自己的methodB()
        methodA(); // 访问父类继承的methodA()
        // methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
    }
}

[Summary] When a member method does not have the same name, when accessing the method in a subclass method or through a subclass object, you will access your own method first. If you do not find it yourself, you will look for it in the parent class. If it does not exist in the parent class, an error will be reported.

  1. Member methods have the same name

public class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}

public class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    
    public void methodC(){
        methodA(); 		// 没有传参,访问父类中的methodA()
        methodA(20); 	// 传递int参数,访问子类中的methodA(int)
        methodB(); 		// 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}

【illustrate】

  • When accessing methods with different names in the parent class and the subclass through the subclass object, the method is first searched in the subclass and accessed if found. Otherwise, the method is searched in the parent class and accessed if found. Otherwise, a compilation error is reported.

  • When accessing the method with the same name of the parent class and the subclass through the derived class object, if the parameter lists of the method with the same name of the parent class and the subclass are different (overloaded), select the appropriate method to access according to the parameters passed by the calling method, if not, an error will be reported.

[Question] If there are members in the subclass that are the same as those in the parent class, how do you access members with the same name in the parent class in the subclass?

[Answer] You need to use the super keyword .

【1.5】super keyword

        Due to poor design or scenario requirements, there may be members with the same name in the subclass and parent class. What should I do if I want to access a member of the parent class with the same name in a subclass method?

        Direct access is not possible. Java provides the super keyword, which is mainly used to access members of the parent class in subclass methods .

/** - 父类
 *
 */
class Base {
    int a;
    int b;

    public void methodA() {
        System.out.println("Base::methodA()");
    }

    public void methodB() {
        System.out.println("Base::methodB()");
    }
}

/** - 子类
 *  - 继承Base类
 */
class Derived extends Base {
    int a;      // 与Base父类中成员变量同名切类型相同。
    char b;     // 与Base父类中成员变量同名切类型不相同。

    // 与Base父类中的methodA函数构成重载。
    public void methodA(int a) {
        System.out.println("Derived::methodA(int a)");
    }

    // 与Base父类中的methodA函数重写(即原型一致,重写后续详细介绍)
    public void methodB() {
        System.out.println("Derived::methodB()");
    }

    public void methodC() {
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 100;    // 相当于this.a
        b = 101;    // 相当于this.b
        // this是当前对象的引用,如果在子类中找不到a | b会继续到父类中找

        // 注意:如果像指定访问父类对象需要使用super关键字来修饰
        super.a = 100;
        super.b = 101;
        // super是只会在父类继承下来中找


        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(10);    // 传参调用子类的的methodA方法
        methodA();         // 没有传参调用父类的methodA方法

        // methodB是父类与子类重名。
        methodB();  // 直接调用会调用子类的methodB
        super.methodB();  // 增加super关键字,会调用父类中的methodB
    }
}

In a subclass method, if you want to explicitly access members of the parent class, you can use the super keyword.

Notes

  1. Can only be used in non-static methods.

  2. In the subclass method, access the member variables and methods of the parent class .

【1.6】Subclass construction method

        Father and son, father and son, there is father first and then son, that is: when constructing a subclass object, you need to call the base class constructor first, and then execute the subclass constructor.

/** - 动物类(父类)
 *
 */
class Animal {
    public Animal(String name, int age, float weight) {
        this._name = name;
        this._age = age;
        this._weight = weight;
    }

    public String _name;
    public int _age;
    public float _weight;
    public void eat() {
        System.out.println(_name + "正在吃饭");
    }

    public void sleep() {
        System.out.println(_name + "正在睡觉");
    }

    public void bark() {
        System.out.println(_name + "汪汪汪~~~");
    }
}

/** - 动物狗类(子类)
 */
class Dog extends Animal {
    public Dog(String name, int age, float weight) {
        super(name, age, weight);   // 先构造父类的构造方法
        // .. 执行子类的构造方法
    }
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

/** - 动物猫类(子类)
 */
class Cat extends Animal {
    public Cat(String name, int age, float weight) {
        super(name, age, weight);   // 先构造父类的构造方法
        // .. 执行子类的构造方法
    }
    
    public void print() {
        System.out.println("我是" + _name + "新的功能");
    }
}

        In the subclass construction method, no code about the base class construction is written, but when constructing the subclass object, the base class construction method is executed first, and then the subclass construction method is executed, because: the members of the subclass object are It consists of two parts, the part inherited from the base class and the part newly added by the subclass. Father and son must have father first and then son, so when constructing a subclass object, you must first call the construction method of the base class to complete the construction of the members inherited from the base class, and then call the subclass's own construction method to complete the construction of the members inherited from the base class. The newly added members of the subclass are initialized completely. 【Notice】

  1. If the parent class explicitly defines a parameterless or default constructor, there is an implicit super() call by default in the first line of the subclass constructor, that is, the base class constructor is called.

  2. If the parent class constructor has parameters, the user needs to explicitly define the constructor for the subclass and select the appropriate parent class constructor to call in the subclass constructor, otherwise the compilation will fail.

  3. In the subclass constructor, when super(...) calls the parent class constructor, it must be the first statement in the subclass constructor.

  4. super(...) can only appear once in the subclass constructor, and cannot appear at the same time as this

【1.7】super and this

        Both super and this can be used to access member methods: member variables and calling other member functions can both be used as the first statement of the constructor. So what is the difference between them?

Similar points

  1. They are all keywords in Java.

  2. It can only be used in non-static methods of a class to access non-static member methods and fields.

  3. When called in a constructor, it must be the first statement in the constructor and cannot exist at the same time.

Differences

  1. this is a reference to the current object, which is the object that calls the instance method. Super is equivalent to a reference to some members of the subclass object inherited from the parent class.

 

  1. In non-static member methods, this is used to access the methods and properties of this class, and super is used to access the methods and properties inherited from the parent class.

  2. In the constructor method: this(...) is used to call the constructor method of this class, and super(...) is used to call the constructor method of the parent class. The two calls cannot appear in the constructor method at the same time.

  3. There must be a call to super(...) in the constructor. The compiler will increase it if the user does not write it, but this(...) will not increase if the user does not write it.

【1.8】Let’s talk about initialization again

        Do we remember the code blocks we talked about earlier? Let's briefly review several important code blocks: instance code blocks and static code blocks. Execution order when there is no inheritance relationship.

class Person {
    // 属性
    public String _name;
    public int _age;
    // 构造函数
    public Person(String name, int age) {
        _name = name;
        _age = age;
        System.out.println("Person::构造代码块");
    }
    // 成员方法
    static {
        System.out.println("Person::静态代码块");
    }

    {
        System.out.println("Person::实例代码块");
    }
}
public class Main {
    public static void main(String[] args) {
        Person perosn = new Person("ShaXiang", 18);
    }
}

【Results of the】

Person::静态代码块
Person::实例代码块
Person::构造代码块
  1. The static code block is executed first and only once, during the class loading phase.

  2. When an object is created, the instance code block will be executed. After the instance code block is executed, the final constructor method will be executed.

[ Execution order based on inheritance relationship ]

class Person {
    // 属性
    public String _name;
    public int _age;
    // 构造函数
    public Person(String name, int age) {
        _name = name;
        _age = age;
        System.out.println("Person::构造代码块");
    }
    // 成员方法
    static {
        System.out.println("Person::静态代码块");
    }

    {
        System.out.println("Person::实例代码块");
    }
}

class Student extends Person {
    public Student(String name, int age) {
        super(name, age);
        System.out.println("Student::构造代码块");
    }

    static {
        System.out.println("Student::静态代码块");
    }

    {
        System.out.println("Student::实例代码块");
    }
}

public class Main {
    public static void main(String[] args) {
        Student student = new Student("ShaXiang", 18);
    }
}

【Results of the】

Person::静态代码块
Student::静态代码块
Person::实例代码块
Person::构造代码块
Student::实例代码块
Student::构造代码块

By analyzing the execution results, the following conclusions are drawn: 1. The parent class static code block is executed prior to the subclass static code block and is executed earliest. 2. The parent class instance code block and the parent class construction method are executed immediately. 3. The instance code block and subclass construction method of the subclass are executed immediately. 4. When the subclass object is instantiated for the second time, the static code blocks of the parent class and the subclass will no longer be executed.

【1.9】protected keyword

In the chapter on classes and objects, in order to realize the encapsulation feature, Java introduces access qualifiers, which mainly limit whether the class or members in the class can be accessed outside the class or in other packages.

 

So what is the visibility of members with different access rights in the parent class in the subclass?

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了。
// 此B类在Package01中。
public class B {
    private int a;
    protected int b;
    public int c;
    int d;
}

// D类与B类在同一个Package包中。
// 此D类在Package01中。
public class D extends B {
    public void method() {
        // super.a = 10;    // 编译报错,父类private成员在相同包子类中不可见。
        super.b = 20;       // 父类中protected成员在相同包子类中可以直接访问。
        super.c = 30;       // 父类中public成员在相同包子类中可以直接访问。
        super.d = 40;       // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问。
    }
}

// C类与B类不在同一个Package包中。
// 此C类在Package02中。
public class C extends B {
    public void method() {
        // super.a = 10;    // 编译报错,父类private成员在相同包子类中不可见。
        super.b = 20;       // 父类中protected修饰的成员在不同包子类中可以直接访问。
        super.c = 30;       // 父类中public成员在相同包子类中可以直接访问。
        // super.d = 40;    // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问。
    }
}

// TestC类与C类在同一个Package包中。
// 此TestC类在Package02中。
public class TestC {
    public static void main(String[] args) {
        C c = new C();
        c.method();

        // System.out.println(c.a);;    // 编译报错,父类中private成员在不同包其他类中不可见。
        // System.out.println(c.b);;    // 父类中protected成员在不同包其他类中不能直接访问。
        System.out.println(c.c);;       // 父类中public成员在不同包其他类中可以直接访问。
        // System.out.println(c.d);;    // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问。
    }
}

[Note] Although private member variables in the parent class cannot be directly accessed in the subclass, they are also inherited into the subclass.

        When should I use which one? We hope that the class should be as "encapsulated" as possible, that is, hide the internal implementation details and only expose the necessary information to the caller of the class. Therefore, we should use comparison as much as possible when using it. Strict access permissions. For example, if a method can be private, try not to use public. In addition, there is a simple and crude method: set all fields as private and all methods as public. However, is this method an abuse of access rights? I still hope that students can think carefully when writing code. , "Who" should use the field methods provided by this class (whether it is used internally by the class itself, by callers of the class, or by subclasses).

【1.10】Inheritance method

 

However, only the following inheritance methods are supported in Java:

 

[Note] Multiple inheritance is not supported in Java.

        Always keep in mind that the classes we write are abstractions of real things. The projects we actually encounter in the company are often more complex in business and may involve a series of complex concepts, all of which require us to use code to represent them, so we really There will also be many classes written in the project. The relationship between classes will also be more complex.

        But even so, we don't want the inheritance hierarchy between classes to be too complicated. Generally, we don't want more than three levels of inheritance relationships . If there are too many inheritance levels, we need to consider refactoring the code.

        If you want to restrict inheritance syntactically, you can use the final keyword.

【1.11】final keyword

The final key can be used to modify variables, member methods, and classes.

  1. Modifies a variable or field to represent a constant (that is, it cannot be modified).

final int a = 10;
a = 20; // 编译出错
  1. Modified class: Indicates that this class cannot be inherited.

final public class Animal {
...
}
public class Bird extends Animal {
...
} 
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

 

The String class we usually use is modified with final and cannot be inherited.

  1. Modified method: indicates that the method cannot be overridden (described later)

【1.12】Inheritance and combination

Similar to inheritance, composition is also a way to express the relationship between classes, and can also achieve the effect of code reuse. Composition does not involve special syntax (keywords such as extends), just using an instance of one class as a field of another class.

Inheritance represents an is-a relationship between objects , for example: a dog is an animal and a cat is an animal.

Combination represents a has-a relationship between objects , such as cars.

 

The relationship between a car and its tires, engine, steering wheel, on-board systems, etc. should be a combination, because the car is composed of these components.

// 轮胎类
class Tire{
	// ...
} 

// 发动机类
class Engine{
    // ...
} 

// 车载系统类
class VehicleSystem{
	// ...
}

class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
    // ...
} 

// 奔驰是汽车
class Benz extend Car{
	// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

        Both combination and inheritance can achieve code reuse. Should you use inheritance or combination, you need to choose according to the application scenario. General advice: use combination as much as possible.

Articles from the Internet: In-depth understanding of composition and inheritance in Java

【2】Polymorphism

【2.1】The concept of polymorphism

        The concept of polymorphism: In layman's terms, it means multiple forms. The specific point is to complete a certain behavior. When different objects complete it, different states will be produced. 

【for example】

 

【for example】

 

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

【2.2】Polymorphic implementation 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. Subclasses must override methods in the parent class .

  3. Call the overridden method through a reference to the parent class .

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 + "吃饭");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(name + "吃鱼~~~~");
    }
}

class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃骨头~~~");
    }
}

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

    public static void main(String[] args) {
        Cat cat = new Cat("元宝", 20);
        Dog dog = new Dog("来福", 21);

        eat(cat);
        eat(dog);
    }
}

        In the above code, the code above the dividing line is written by the implementer of the class , and the code below the dividing line is written by the caller of the class . When the caller of the class writes the eat method, the parameter type is Animal ( parent class), at this time, it does not , nor does it pay attention to which type (which subclass) instance the current a reference points to. At this time, when the reference a calls the eat method, there may be many different performances. (relative to the instance referenced by a), this behavior is called polymorphism

 

【2.3】Rewriting

        Override: Also called overwriting. Rewriting is a subclass's rewriting of the non-static, non-private modification, non-final modification, non-constructor method implementation process of the parent class. Neither the return value nor the formal parameters can be changed. That is, the shell remains unchanged and the core is rewritten! The advantage of overriding is that subclasses can define their own behavior as needed. In other words, subclasses can implement the methods of the parent class as needed.

[ Rules for method rewriting ]

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

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

  • Access permissions cannot be lower than the access permissions of overridden methods in the parent class. For example: If a parent class method is modified by public, then the method overridden in the subclass cannot be declared as protected.

  • Methods and constructors modified by static or private in the parent class cannot be overridden.

  • Overridden methods can be explicitly specified using the @Override annotation. This annotation can help us perform some legality checks. For example, if the method name is accidentally spelled incorrectly (such as aet), then the compiler will You will find that there is no aet method in the parent class, and a compilation error will be reported, indicating that overriding cannot be constituted.

[ The difference between rewriting and overloading ]

Difference override override
parameter list Must not be modified Must be modified
Return type Must not be modified [unless a parent-child relationship can be formed] Can be modified
access qualifier Must not make stricter restrictions (restrictions can be lowered) Can be modified

That is: method overloading is a polymorphic manifestation of a class, while method overriding is a polymorphic manifestation of a subclass and a parent class.

[ Rewritten design principles ]

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

        For example: Mobile phones a few years ago could only make calls and send text messages, and the caller ID could only display numbers. However, today's mobile phones can not only display numbers, but also avatars, regions, etc. when displaying caller ID. During this process, we should not make modifications to the original old class, because the original class may still be used by users . The correct approach is: create a new mobile phone class and rewrite the caller ID method. , thus meeting our current needs .

 

Static binding: Also known as early binding (early binding), that is, at compile time, the specific method to be called is determined based on the type of actual parameters passed by the user. Typical representative function overloading.

Dynamic binding: Also known as late binding (late binding), that is, the behavior of the method cannot be determined at compile time. You need to wait until the program is running to determine the specific method of calling that class.

【2.4】Upward transfer and downward transformation

【2.4.1】Upward transformation

Upward transformation: It actually creates a subclass object and uses it as a parent class object. Syntax format: parent class type object name = new subclass type ().

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

Animal is a parent class type, but it can reference a subclass object because it is converted from a small range to a large range.

 

【scenes to be used】

  • direct assignment

  • Method passing parameters

  • method returns

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 + "吃饭");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat(){
        System.out.println(name + "吃鱼~~~~");
    }
}

class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃骨头~~~");
    }
}

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

    public static Animal buyAnimal(String strVar) {
        if("狗".equals(strVar)) {
            return new Dog("来福", 21);
        } else if("猫".equals(strVar)) {
            return new Cat("元宝", 20);
        } else {
            return new Animal("动物", 0);
        }
    }

    public static void main(String[] args) {
        // 1、直接赋值
        Animal animal01 = new Animal("动物", 0);
        animal01.eat(); // 动物吃饭

        Animal animal02 = new Cat("元宝", 20);
        animal02.eat(); // 元宝吃鱼~~~~

        Animal animal03 = new Dog("来福", 21);
        animal03.eat(); // 来福吃骨头~~~

        // 2、方法传参
        eat(animal01);  // 动物吃饭
        eat(animal02);  // 元宝吃鱼~~~~
        eat(animal03);  // 来福吃骨头~~~

        // 3、方法返回值
        Animal animal = buyAnimal("动物");
        animal.eat();   // 动物吃饭
        Animal cat = buyAnimal("猫");
        cat.eat();      // 元宝吃鱼~~~~
        Animal dog = buyAnimal("狗");
        dog.eat();      // 来福吃骨头~~~
    }
}

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

Defects of upward transformation: methods unique to subclasses cannot be called.

【2.4.2】Downward transformation

When a subclass object is used as a parent class method after upward transformation, the subclass method can no longer be called. However, sometimes you may need to call a subclass-specific method. In this case: restore the parent class reference to the subclass object. , i.e. downward conversion.


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();
        // 向上转型
        // 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
        
        // animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.bark();
    }
}

        Downcasting is rarely used and is unsafe. 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();
        } 
        
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

Official introduction to instanceof keyword: Chapter 15. Expressions

【2.5】Advantages and disadvantages of polymorphism


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("❀");
    }
}

[ Benefits of using polymorphism ]

  1. It can reduce the "cyclomatic 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 straightforward, it is relatively simple and easy to understand. If there are many conditional branches or loop statements, it is considered to be more complicated to understand.

  • Therefore, we can simply and crudely calculate the number of conditional statements and loop statements in a piece of code. This number is called "cyclomatic 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 code. Generally, it will not exceed 10.

For example, what we need to print now is not 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();
    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. The code is simpler. The implementation code is as follows:

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

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

class Triangle extends Shape {
	@Override
	public void draw() {
		System.out.println("△");
	}
}
  • For the caller of the class (drawShapes method), it only needs to create an instance of a new class, and the cost of modification is very low.

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

[Polymorphic defect]

  • The running efficiency of the code is reduced.

  • There is no polymorphism in attributes. When both the parent class and the child class have attributes with the same name, only the parent class's own member attributes can be referenced through the parent class reference.

  • There is no polymorphism in constructors [see Section 2.6].

【2.6】Avoid calling overridden methods in the constructor

A piece of code with pitfalls. We create two classes, B is the parent class, and D is the subclass. Override the func method in D. And call func in the constructor of B

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();
    }
} 
// 执行结果
D.func() 0
  • When constructing the D object, the constructor of B will be called.

  • The func method is called in the constructor of B. At this time, dynamic binding will be triggered and the func in D will be called.

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

  • Therefore, within the constructor, try to avoid using instance methods, except for final and private methods.

[Conclusion] "Use the simplest way possible to bring the object into a working state" and 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 been constructed yet) Complete), there may be some hidden problems that are extremely difficult to find.

Guess you like

Origin blog.csdn.net/lx473774000/article/details/131928999
Recommended