Day13 | Java多态详解

69 阅读5分钟

在上一篇文章[Day12 | Java继承详解]中,我们讲了一下"继承"怎么让代码复用变得简单。

今天我们来看一下"多态"怎么让代码更具有灵活性和扩展性。

多态也是面向对象三大特性中的核心特性之一。

一、多态是什么

多态字面理解就是多种形态。在Java中,它允许我们使用父类类型的引用,来指向子类对象,并根据实际对象类型来调用方法。

还是来看我们经常使用的动物类的案例:

package com.lazy.snail.day13;

/**
 * @ClassName Animal
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/28 10:26
 * @Version 1.0
 */
public class Animal {
    public void shout() {
        System.out.println("动物叫");
    }
}

父类Animal定义了一个shout方法。

package com.lazy.snail.day13;

/**
 * @ClassName Dog
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/28 10:26
 * @Version 1.0
 */
public class Dog extends Animal {
    @Override
    public void shout() {
        System.out.println("狗叫:汪汪汪");
    }
}

package com.lazy.snail.day13;

/**
 * @ClassName Cat
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/28 10:27
 * @Version 1.0
 */
public class Cat extends Animal {
    @Override
    public void shout() {
        System.out.println("猫叫:喵喵");
    }
}

Cat类和Dog都通过extends关键字继承Animal类。

并且重写(@Override)了Animal中的shout方法。

package com.lazy.snail.day13;

/**
 * @ClassName Day13Demo
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/5/28 10:27
 * @Version 1.0
 */
public class Day13Demo {
    public static void main(String[] args) {
        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.shout();
        a2.shout();
    }
}

在测试Demo中,我们并没有使用Dog dog = new Dog();和Cat cat = new Cat();创建猫狗对象。

而是直接使用了父类Animal指向了创建的子类猫狗的对象。

当使用对象.方法时,调用了子类对象的方法。

方法调用看对象实际类型,而不是变量声明类型。

二、多态的前提条件

整理了一下多态的一些前提条件:

条件说明
继承必须有父类与子类关系
方法重写子类必须重写父类的方法
父类引用指向子类对象Animal a = new Dog();

根据上面的案例:

1、UML类图,Cat和Dog类均继承Animal

2、Cat和Dog类均重写了父类Animal中的shout方法

3、Demo中创建对象时,均使用父类Animal指向子类对象

三、核心原理与引用类型转换

1、编译看左边,运行看右边

Animal a = new Dog();
a.shout(); 

编译阶段只看左边(Animal):只要Animal有shout方法,编译就通过;

运行阶段看右边(Dog):实际调用的是Dog的shout方法。

方法调用是动态绑定:运行时决定

如果把Animal中的shout方法注释(IDEA多行注释:选中代码块,Ctrl + Shift + /)

Demo中就会出现编译错误:

明确的指出了编译阶段,没办法在Animal中找到shout方法。

如果把Dog类中的shout方法注释(记得把Animal中shout方法的注释取消)。

再运行Demo,会看到dog对象调用shout方法后输出的是“动物叫”。

2、向上转型

Animal a = new Dog(); // 自动进行,不需要强转

安全、常见,是多态的基础。

但只能调用父类声明过的方法(即使子类重写了也可调用)

3、向下转型

Animal a = new Dog();
Dog d = (Dog) a; // 强制类型转换
d.shout();

有风险,可能ClassCastException,最好使用instanceof判断:

if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.shout();
}

小结一下:

操作方向安全性用途
向上转型子 → 父安全、自动支持多态统一编程
向下转型父 → 子有风险、需强转使用子类特有方法或属性

四、多态的变量访问

多态下变量的访问是静态绑定的,也就是只看编译时的类型,不会动态派发。

class Animal {
    String name = "动物";
}

class Dog extends Animal {
    String name = "狗";
}

public class Demo {
    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println(a.name);
    }
}

父子类中都定义了name属性。

Animal a =newDog();满足多态的条件,a.name输出的是“动物”。

编译阶段的类型是Animal,绑定的就是Animal中的name属性(看左边的类型)。

五、虚方法调用机制

Java中除了static、private、final修饰的方法外,其它方法都被视为虚方法,即运行时动态绑定。

当你写:

Animal a = new Dog();
a.shout();

JVM实际会在运行时根据对象的类型,从它的虚方法表中查找对应实现,然后调用。

后续讲到JVM的时候,会继续讲

六、多态应用场景

1、接口与抽象类的统一编程

public void startAnimal(Animal a) {
    a.shout(); // 不关心具体类型,统一接口
}

方法可以接受任意Animal子类,如Dog、Cat、Tiger等等。

2、面试常见:方法调用顺序

class A {
    public void show() {
        System.out.println("A.show()");
    }
}
class B extends A {
    public void show() {
        System.out.println("B.show()");
    }
}
class C extends B {
    public void show() {
        System.out.println("C.show()");
    }
}

public class Test {
    public static void main(String[] args) {
        A obj = new C();
        obj.show();
    }
}

看看这个案例,理一下,脑跑一下代码,main方法中的obj.show会输出什么?

结语

小结下一本文关于多态的知识点:

概念要点
多态同一父类引用,调用不同子类的方法实现
条件继承 + 方法重写 + 向上转型
编译 vs 运行编译看变量类型,运行看对象实际类型
动态绑定运行时确定方法实现,提高灵活性
变量访问静态绑定,编译期确定

多态实际就是屏蔽具体实现差异,统一对外接口。 在后续的代码实践中,我们会在使用多态的地方继续了解多态的魅力。

下一篇预告

Day14 | 抽象类和接口

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》