多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

多态的本质是父类引用指向子类对象,多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。再举个比较形象的例子:
比如有一个函数是叫某个人来吃饭,函数要求传递的参数是人的对象,可是来了一个美国人,你看到的可能是用刀和叉子在吃饭,而来了一个中国人你看到的可能是用筷子在吃饭,这就体现出了同样是一个方法,可以却产生了不同的形态,这就是多态。

在讲解多态之前,我们先了解几个概念:数据类型、引用类型

数据类型的作用

不同的数据类型在内存中占用的空间大小不同,Java 数据类型明确了每种类型的数据在内存中如何存储以及需要多少存储空间,这有助于合理使用内存资源。

  • 基本数据类型:
    • byte 类型占用 1 个字节(8 位),1比特代表符号位,7比特代表数值位
    • int 类型占用 4 个字节(32 位),1比特代表符号位,31比特代表数值位
    • float 类型占用 4 个字节(32 位),1比特代表符号位,8比特代表阶位,23比特代表我们当前的数值位

结论:不同的数据类型在内存中的存储形式是不一样的,也就是说数据类型决定了数据在内存当中的存储形式

引用类型和实际对象类型共同影响着对内存中数据结构的访问和使用

代码示例:

class Animal {
    public void eat() {
        System.out.println("动物在吃东西");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫在吃鱼");
    }

    public void meow() {
        System.out.println("喵喵喵");
    }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        // 引用类型是 Animal,实际对象类型是 Cat
        Animal animal = new Cat();
        // 虽然引用类型是 Animal,但内存中的对象是 Cat 类型
        // 该对象拥有 Cat 类定义的所有属性和方法
    }
}

在上述代码中,animal 引用的对象在内存中是 Cat 类型,包含 Cat 类自身的属性和方法以及从 Animal 类继承来的属性和方法。

引用类型影响对对象的访问权限

引用类型限制了可以访问对象的哪些成员。只能通过引用类型调用其类型中定义或继承的方法和成员变量。 还是上面的例子,由于 animal 的引用类型是 Animal,所以只能调用 Animal 类中定义的方法,如 eat(),而不能直接调用 Cat 类特有的 meow() 方法。

animal.eat(); // 可以正常调用
// animal.meow(); // 编译错误,因为 Animal 类型没有 meow 方法
通过内存图来解释父类引用指向子类对象

下列代码的输出结果是什么:

继承关系:B类继承A类、C类和D类继承B类

执行第一条语句,在内存图中会生成A的对象,调用A中的无参方法后输出"无参的A"

执行第二条语句,A类类型指向B类的对象,也就是我们所说的多态

因为B类继承A类,在内存中先创建A类对象再创建B类对象,A类中有show()方法,B类中也有show()方法,因为引用类型限制的可以调用的方法,只能调用 A 类中定义的方法,如果B中没有show()方法的话,结果一定是输出"无参的A",问题在于子类中也有show()方法,这个问题引出了一个概念,叫做动态绑定机制

动态绑定机制

在 Java 中,对于被重写的方法,在运行时会根据实际对象的类型来决定调用哪个类中的方法,而不是根据引用类型。这意味着即使引用类型是父类,但只要实际对象是子类,并且子类重写了父类的方法,那么调用该方法时就会执行子类的实现。

由此我们知道,在子类和父类同时具有某个方法时,是根据实际对象的类型来调用方法的,所以应该调用B类的show()方法,输出"无参的B"

执行第三条语句,B类类型指向B类方法,在内存中先创建父类对象再创建子类对象,子类对象可以直接调用自身方法和父类方法,B类本身有show()方法,所以直接输出"无参的B"

通过上述代码讲解,相信大家对多态已经有了一定程度的了解,下面我们做几道练习题来加深对于多态和动态绑定机制的了解(还是上述代码)

System.out.println(a1.show(b1)); //结果输出什么

我们知道内存中现在已经创建了A和B类的对象,调用A类的带参方法,但b1的类型是B类类型,在A类中并没有B类类型的参数传递,但实际上b1可以向上转型成A类类型,该参数叫做多态参数。

多态参数

在 Java 中,当一个方法的参数类型为某个父类类型时,该方法可以接收该父类的任何子类对象作为实参。在方法内部调用父类中被重写的方法时,会根据传入的实际子类对象类型,动态地调用子类中重写后的方法,而不是父类中的原始方法

使用多态参数的好处
  • 代码复用:通过多态参数,一个方法可以处理多种不同类型的对象,避免了为每个子类都编写一个单独的方法,提高了代码的复用性。
  • 可扩展性:当需要添加新的子类时,不需要修改现有的方法,只需要让新的子类继承自父类并重写相应的方法即可。这样可以很方便地扩展程序的功能。
  • 灵活性:多态参数使得程序在运行时可以根据实际情况动态地选择调用不同的方法,增强了程序的灵活性和可维护性。

更多推荐