Skip to content

多态

多态概念

开闭原则

程序应该对扩展开放,对修改源代码关闭。

java
多态:多种形态,多种状态。
    
同一个事物,由于条件不同,产生的结果也不同。不同游戏中按键功能也不同。例如:一个按键,在开大招的时候,有不同的效果,例如异化师,扇子妈,萨乐芬妮等英雄。

多态的概念

ts
多态:同一个引用类型,使用不同的实例而执行不同操作。

多态的前提

java
继承和方法重写:子类继承父类,子类重写父类的方法。

向上转型

父类引用指向子类对象属于向上转型,此时通过父类引用可以访问的是子类重写或者继承父类的方法,但不能访问子类独有的方法。

多态向上转型的情况:存在以下任一种就称之为多态。

java
情形1. 父类作为形参,子类作为实参
情形2. 父类作为声明返回值,实际返回值为子类类型
情形3. 父类类型的数组、集合,元素为子类类型

情形1

1.父类作为形参,子类作为实参

java
package com.moreStatus;

public class Pet {
    protected String name;
    protected int health;
    protected int love;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHealth() {
        return health;
    }

    public void setHealth(int health) {
        this.health = health;
    }

    public int getLove() {
        return love;
    }

    public void setLove(int love) {
        this.love = love;
    }

    public Pet(String name, int health, int love) {
        this.name = name;
        this.health = health;
        this.love = love;
    }
    public Pet() {
    }
    public void print(){
        System.out.println("宠物的名字是:" + name);
        System.out.println("宠物的健康值是:" + health);
        System.out.println("宠物的亲密值是:" + love);
    }

    public void cure(){
        System.out.println("宠物看病");
    }


}
java
package com.moreStatus;

public class Dog extends Pet{
    private String strain;

    public String getStrain() {
        return strain;
    }

    public void setStrain(String strain) {
        this.strain = strain;
    }

    public Dog(String name, int health, int love, String strain) {
        super(name, health, love);
        this.strain = strain;
    }

    public Dog() {}

    // 重写父类看病方法

    @Override
    public void cure() {
        System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
        this.setHealth(100);
    }
}
java
package com.moreStatus;

public class Penguin extends Pet{
    private char gender;

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public Penguin(String name, int health, int love, char gender) {
        super(name, health, love);
        this.gender = gender;
    }

    public Penguin() {
    }

    // 重写父类方法

    @Override
    public void cure() {
        System.out.println("企鹅看病,打针,吃小鱼,健康值恢复");
        this.setHealth(100);

    }

}
java
package com.moreStatus;

public class Master {
    public void toHospitalWithDog(Dog dog){
        dog.cure();
    }

    public void toHospitalWithPenguin(Penguin penguin){
        penguin.cure();
    }

    // 开闭原则 : 程序应该对扩展开放 对修改源代码关闭
    // 问题分析:以上代码编写了两个方法分别用于给不同的宠物子类看病 这种方式不符合开闭原则
    // 如果后续有更多的宠物子类 那么还需要编写更多的方法来实现
    // 解决方案:使用多态解决 我们应该编写一个方法 实现给所有的宠物子类看病
    public void toHospitalWithPet(Pet pet){ // Pet pet = new Dog(); = new Penguin();
        pet.cure();
    }
}
java
package com.moreStatus;

public class Test {
    public static void main(String[] args) {
        Master mas = new Master();
        Dog dog1 = new Dog("十一月",99,99,"金毛");
        mas.toHospitalWithPet(dog1);
        // 打印宠物现在的信息
        dog1.print();
    }
}

情形2

2.父类作为声明返回值,实际返回值为子类类型

java
package com.moreStatus;

public class Master {

    // 获得 一个 企鹅对象
    public Penguin getPenguin(){
        Penguin penguin = new Penguin("小白", 100, 100, '雄');
        return penguin;
    }
    // 获得 一个 狗狗对象
    public Dog getDog(){
        Dog dog = new Dog("大黄", 100, 100, "金毛");
        return dog;
    }
    // 获得 一个 宠物对象
    // 使用多态的方式
    // 父类作为声明返回值,实际返回值为子类类型
    public Pet getPet(String str){
        if (str.equals("No1")){
            Penguin penguin = new Penguin("小白", 100, 100, '雄');
            return penguin;
        } else{
            Dog dog = new Dog("大黄", 100, 100, "金毛");
            return dog;
        }
    }
}
java
package com.moreStatus;

public class Test {
    public static void main(String[] args) {
        Master mas = new Master();

        Pet pet = mas.getPet("No1");
        System.out.println("获得的奖品是 "+pet.name);
    }
}

情形3

3.父类类型的数组、集合,元素为子类类型。

java
package com.moreStatus;

public class Test {
    public static void main(String[] args) {
        Pet [] pets = new Pet[3];
        pets[0] = new Dog("十一月",98,89,"金毛");
        pets[1] = new Dog("十二月",98,89,"金毛");
        pets[2] = new Penguin("一月",98,89,'雄');
        pets[0].print();
        pets[1].print();
        pets[2].print();
    }
}

多态向上转型的使用事项

java
父类引用指向子类对象,此时通过父类引用,在对象使用多态的方式调用方法时,可以访问的是子类重写或者继承父类的方法,不能访问子类独有的方法

向下转型

技术背景问题

java
父类引用指向子类对象属于向上转型,此时通过父类引用,可以访问的是子类重写或者继承父类的方法但不能访问子类独有的方法,如需访问,则必须向下转型。

如需访问子类独有的方法,必须向下转型

多态向下转型的含义和注意

java
向下转型:
    
是将指向子类对象的父类引用 转换为 子类类型,而不是 将指向父类对象的父类引用 转换为子类类型
    
例如:
    // 正确的
    Pet pet = new Dog();
	Dog dog = (Dog) pet;
	// 下边的是错误的
	Pet pet = new Pet(); // 没有向上转型
	Dog dog = (Dog) pet;

总结:必须先向上转型 才可以向下转型  否则将出现类型转换异常  ClassCastException

多态向下转型示例

java
// 先向上转型
Pet pet = new Dog();

if(pet instanceof  Dog){
    // pet 就是 子类对象的父类引用对象,将pet再转为Dog子类属性
    Dog dog = (Dog)pet;
    // 这样就可以使用子类对象中的属性了
    dog.playFlyDisc();
}

使用 instanceof 关键字进行类型检查

java
因为异常会中断程序 所以 在实际开发中我们会使用instanceof关键字 在类型转换之前进行判断 如果类型正确 则转换 不正确 则不转
java
instanceof 对象类型检查判断
参数:无
返回值:boolean
    
例子:
对象名 instanceof 类名
Person p1 = new Person()
boolean result = p1 instanceof Dog

多态案例

java
package com.moreStatus;

/*
 * 父类引用指向子类对象属于向上转型,此时通过父类引用,
 *  可以访问的是子类重写或者继承父类的方法
 *  不能访问子类独有的方法 如需访问 则必须向下转型
 *
 * 向下转型:
 * 是将指向子类对象的父类引用 转换为 子类类型
 * 而不是 将指向父类对象的父类引用 转换为子类类型
 * 总结:必须先向上转型 才可以向下转型  否则将出现类型转换异常  ClassCastException
 *
 * 因为异常会中断程序 所以 在实际开发中我们会使用instanceof关键字 在类型转换之前
 * 进行判断 如果类型正确 则转换 不正确 则不转
 *  用法: 对象名 instanceof 类名
 *  表示判断左侧的对象是否属于右侧的类型
 */

public class Test {
    public static void main(String[] args) {
        // 创建宠物类
        Pet pet = new Dog("十一月",98,39,"乌萨齐"); // 向上转型
        // 判断类型
        if (pet instanceof Dog){ // 判断类型正确, pet 属于 Dog类型
            // pet对象强转为dog对象 === 父类引用转为子类对象
            Dog dog = (Dog) pet;
            // 使用向下转型,调用子类中独有的方法
            dog.playFlyDisc();
        }
        // 没有向上转型
        Pet p1 = new Pet();
        if(p1 instanceof  Dog){ // 判断类型不正确, pet 不属于 Dog类型
            Dog dog1 = (Dog)p1; // 转型错误 有异常 ClassCastException 报错
            System.out.println("dog1 = " + dog1);
        }else{
            System.out.println("类型不匹配");
        }
        System.out.println("程序结束");
    }
}
java
package com.moreStatus;

public class Pet {
    protected String name;
    protected int health;
    protected int love;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHealth() {
        return health;
    }

    public void setHealth(int health) {
        this.health = health;
    }

    public int getLove() {
        return love;
    }

    public void setLove(int love) {
        this.love = love;
    }

    public Pet(String name, int health, int love) {
        this.name = name;
        this.health = health;
        this.love = love;
    }
    public Pet() {
    }
    public void print(){
        System.out.println("宠物的名字是:" + name);
        System.out.println("宠物的健康值是:" + health);
        System.out.println("宠物的亲密值是:" + love);
    }

    public void cure(){
        System.out.println("宠物看病");
    }


}
java
package com.moreStatus;

public class Dog extends Pet{
    private String strain;

    public String getStrain() {
        return strain;
    }

    public void setStrain(String strain) {
        this.strain = strain;
    }

    public Dog(String name, int health, int love, String strain) {
        super(name, health, love);
        this.strain = strain;
    }

    public Dog() {}

    // 重写父类看病方法

    @Override
    public void cure() {
        System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
        this.setHealth(100);
    }
    // 狗狗玩飞盘方法
    public void playFlyDisc(){
        System.out.println("狗狗玩飞盘");
    }
}
java
package com.moreStatus;

public class Penguin extends Pet{
    private char gender;

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public Penguin(String name, int health, int love, char gender) {
        super(name, health, love);
        this.gender = gender;
    }

    public Penguin() {
    }

    // 重写父类方法

    @Override
    public void cure() {
        System.out.println("企鹅看病,打针,吃小鱼,健康值恢复");
        this.setHealth(100);

    }

    // 企鹅冲QB方法
    public void payQB(){
        System.out.println("企鹅冲了100QB");
    }

}

多态补充

我们观察重写Object类中的equals方法,父类中的方法实现形参为Object类型,所以我们重写形参也必须为Object类型,但是这样我们通过父类类型的形参就无法访问子类中的属性或者方法,所以我们在方法中必须向下转型。

ts
父类写为Object类型,是为了子类的通用性。
子类在重写父类方法中又向下转型,是为了实用性。

Person类中equals重写,运用多态。

java
package com.rewrite;

public class Person {
    private String name;
    private String idCard;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    public Person(String name, String idCard) {
        this.name = name;
        this.idCard = idCard;
    }

    public Person() {
    }

    public void printInfo(){
        System.out.println("我的名字是" + name);
        System.out.println("我的id是" + idCard);
    }

    @Override
    // 自定义类的equals方法重写
    public boolean equals(Object obj){ // 调用的过程 符合向上转型
        // 首先判断地址
        if(this == obj){
            return true;
        }
        // 加一层判断,如果类型不相同直接返回false
        if (obj instanceof  Person){
            // 向下转型(强制类型转换),要使用子类对象的属性
            Person p1 = (Person) obj;
            if (this.name.equals(p1.name) && this.idCard.equals(p1.idCard)){
                return true;
            }
        }
        return false;
    }

    // 自定义类重写哈希值计算
    // 要保证在equals比较为true的情况下 两个对象hashCode相同
    // 上边重写的 equals 方法是根据人的名字和身份证号比较的
    // 所以我们也要根据人的名字和身份证号来计算hash值
    @Override
    public int hashCode() {
        int result = 1; // 哈希值结果
        int prime = 31; // 权重
        result = result * prime + (this.name == null ? 0 : this.name.hashCode());
         // 这里使用了 String类重写的hashCode方法
        result = result * prime + (this.idCard == null ? 0 : this.idCard.hashCode());
        return result;
    }
}

实现原理

java
多态原理:是由虚方法和动态绑定来实现的

具体虚方法是什么:

ts
虚方法(Virtual Method)和非虚方法(Non Virtual Method)

虚方法是指在编译期间,无法确定方法版本信息的这一类方法。

例如 
Pet pet = new Dog(),Pet pet 这一过程,pet的指向就不确定,
pet有可能是 new Cat()的实例,这就无法确定实例是哪一个实例。

可以被子类重写的方法,会在多个子类中进行重写,而new对象的操作是在程序运行期间才执行的(需要动态的开辟空间),Drink drink = new Drink("yousa");
所以在编译阶段,唯独可以确定的是等号左侧的类型,而不能确定的是等号右侧的对象。
因此,可以被子类重写(可以被子类继承的实例方法)的方法就属于虚方法。

虚方法调用底层是通过JVM指令:#invokevritual

非虚方法是指在编译期间可以确定方法版本信息的这一类方法。
比如:静态方法、private修饰的方法、final修饰的方法、构造方法。这些都不可以被子类重写。

非虚方法调用底层是通过JVM指令:#invokespecial

image-20250705094743407

动态绑定和静态绑定是什么

ts
虚方法属于动态绑定:因为在编译期间无法确定方法的版本信息(还没有 new 对象),
所以必须在程序运行过程中才确定调用哪个类中的方法,所以虚方法属于动态绑定。

非虚方法属于静态绑定:在编译期间就可以确定方法的版本信息,实现静态绑定。

非虚方法静态绑定示例:

java
package com.moreStatus;

public class Dog extends Pet{
    private String strain;

    public String getStrain() {
        return strain;
    }

    public void setStrain(String strain) {
        this.strain = strain;
    }

    public Dog(String name, int health, int love, String strain) {
        super(name, health, love);
        this.strain = strain;
    }

    public Dog() {}

    // 重写父类看病方法

    @Override
    public void cure() {
        System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
        this.setHealth(100);
    }
    // 狗狗玩飞盘方法
    public void playFlyDisc(){
        System.out.println("狗狗玩飞盘");
    }
    // 静态方法是非虚方法
    public static void swimming(){
        System.out.println("狗狗在游泳");
    }
}
java
package com.moreStatus;

public class Test01 {
    public static void main(String[] args) {
        // 创建宠物类
        Pet pet = new Dog("十一月",98,39,"乌萨齐"); // 向上转型
        // 判断类型
        if (pet instanceof Dog){ // 判断类型正确, pet 属于 Dog类型
            // pet对象强转为dog对象 === 父类引用转为子类对象
            Dog dog = (Dog) pet;
            // 使用向下转型,调用子类中独有的方法
            dog.playFlyDisc();
            // 静态方法 是 非虚方法,不需要创建对象,直接在加载类的时候初始化
            Dog.swimming();
        }
    }
}

方法覆盖和方法隐藏

ts
实例方法属于覆盖,即重写,也就是子类重写父类方法以后通过子类对象再无法访问父类中被覆盖的方法。

静态方法属于隐藏,子类可以写同名同参数同返回值的静态方法,只是对父类相同静态方法的隐藏,无法覆盖。
因为通过指向对象的父类引用还可以继续访问父类中的静态方法。

方法覆盖和方法隐藏示例:

java
package com.moreStatus;

public class Pet {
    protected String name;
    protected int health;
    protected int love;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHealth() {
        return health;
    }

    public void setHealth(int health) {
        this.health = health;
    }

    public int getLove() {
        return love;
    }

    public void setLove(int love) {
        this.love = love;
    }

    public Pet(String name, int health, int love) {
        this.name = name;
        this.health = health;
        this.love = love;
    }
    public Pet() {
    }
    public void print(){
        System.out.println("宠物的名字是:" + name);
        System.out.println("宠物的健康值是:" + health);
        System.out.println("宠物的亲密值是:" + love);
    }

    public void cure(){
        System.out.println("宠物看病");
    }
    
    // 父类中定义一个吃饭的静态方法
    public static void eat(){
        System.out.println("宠物吃饭");
    }
    
}
java
package com.moreStatus;

public class Dog extends Pet{
    private String strain;

    public String getStrain() {
        return strain;
    }

    public void setStrain(String strain) {
        this.strain = strain;
    }

    public Dog(String name, int health, int love, String strain) {
        super(name, health, love);
        this.strain = strain;
    }

    public Dog() {}

    // 重写父类看病方法
    @Override
    public void cure() {
        System.out.println("狗狗看病,吃药,吃骨头,健康值恢复");
        this.setHealth(100);
    }
    // 狗狗玩飞盘方法
    public void playFlyDisc(){
        System.out.println("狗狗玩飞盘");
    }
    // 非虚方法
    public static void swimming(){
        System.out.println("狗狗在游泳");
    }
    // 子类中定义一个相同的吃饭的静态方法
    public static void eat(){
        System.out.println("狗狗吃骨头");
    }
}
java
package com.moreStatus;

public class Test01 {
    public static void main(String[] args) {
        // 创建宠物类
        Pet pet = new Dog("十一月",98,39,"乌萨齐"); // 向上转型
        Dog dog = new Dog("艾特",97,56,"阿拉斯加");
        pet.cure(); // 调用的是子类中重写的实例方法
        pet.eat(); // 调用的是父类中static方法,通过父类引用指向子类对象的调用。
        dog.eat(); // 调用的是子类中static方法,通过子类引用指向子类对象的调用。父类的static方法隐藏了
    }
}

方法表是什么

java
方法表是一个存在于类信息文件中的数组,这个数组保存当前类中的方法、继承以及重写的方法。
当我们访问某一个方法时,先从本类中查找,本类中没有,继续向父类中查找,直到找打为止。

java命令

ts
// 查看当前class文件详细信息
javap -verbose 编译后文件名
例如:
javap -verbose Note.class

关于 this 的问题:

为什么this存在于实例方法,构造方法中确无法在静态方法中获取this

ts
原因1:
静态方法是在类文件加载到方法区时就进行执行了,而实例方法只有在new出来对象,对象调用时,
才执行,两者的执行时机不同,在静态方法中执行this这时,对象还没有创建。


原因2:
this 被设计成了一个隐式参数,存在于本类中的所有实例方法和构造方法中,
所以我们在实例方法以及构造方法中才可以使用。
静态方法中没有添加此隐式参数,所以无法使用this。
super也存在于this中,所以也无法访问super。

伪代码

java
// 这里相当于有一个 Pet this的隐式参数。
public Pet(Pet this,String name, int health, int love) {
    this.name = name;
    this.health = health;
    this.love = love;
}

通过Java命令查看编译后的类文件详细信息,全参构造方法中有this参数

image-20250705101721388

静态方法中没有this隐式参数。