Skip to content

Chapter 10 · 多态

同一句话,不同对象有不同反应 — 这就是多态。


🎯 本章目标

  • 理解多态的本质与意义
  • 掌握 父类引用指向子类对象 的写法
  • 区分 向上转型 vs 向下转型
  • instanceof 安全转型
  • 知道为什么多态是 OOP 三大支柱之一

1. 什么是多态?

同一个动作,不同对象有不同的表现。

生活例子:

角色听到"叫一声!"
"汪汪!"
"喵喵!"
"哞~"
"嘎嘎!"

代码:

java
class Animal {
    void sound() { System.out.println("某种声音"); }
}
class Dog extends Animal { void sound() { System.out.println("汪汪!"); } }
class Cat extends Animal { void sound() { System.out.println("喵喵!"); } }
class Cow extends Animal { void sound() { System.out.println("哞~"); } }

Animal[] zoo = { new Dog(), new Cat(), new Cow() };
for (Animal a : zoo) {
    a.sound();          // 每个对象输出自己的声音!
}

输出:

汪汪!
喵喵!
哞~

💡 关键:变量类型是 Animal,但实际对象是 Dog/Cat/Cow,调 sound()运行时根据真实类型 决定调哪个版本。

多态原理


2. 多态的三个前提

1. 继承(或实现接口)
2. 方法重写
3. 父类引用指向子类对象

缺一不可:

java
Animal a = new Dog();       // ✅ 父类引用指向子类对象
a.sound();                   // 调子类的 sound(重写)

3. 向上转型(Upcast)

子类对象 → 父类引用自动,永远安全:

java
Dog d = new Dog();
Animal a = d;            // 自动向上转型
a.sound();               // OK:调 Dog.sound()
// a.fetch();            // ❌ 编译错误!Animal 没有 fetch 方法

💡 为什么有用:写一个方法接收 Animal[],可以传 Dog[]Cat[]Cow[] ……所有动物都能处理。


4. 向下转型(Downcast)

父类引用 → 子类引用,需要 强转,可能 ClassCastException

java
Animal a = new Dog();
Dog d = (Dog) a;          // 强转回 Dog
d.fetch();                // 现在可以调 Dog 独有的方法

Animal a2 = new Cat();
Dog d2 = (Dog) a2;        // ❌ 运行时抛 ClassCastException

用 instanceof 防翻车

java
Animal a = new Dog();
if (a instanceof Dog d) {        // JDK 16+ 模式匹配
    d.fetch();                    // 自动转型
}

// 老写法
if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.fetch();
}

向上 / 向下转型


5. 编译时类型 vs 运行时类型

java
Animal a = new Dog();
//   ↑                ↑
// 编译时类型      运行时类型

口诀

  • 编译看左边:能调什么方法、字段,由"声明类型"决定
  • 运行看右边:方法实际执行哪个版本,由"真实对象"决定(虚方法)
java
class Animal {
    String type = "动物";
    void sound() { System.out.println("某种声音"); }
}
class Dog extends Animal {
    String type = "狗";       // 字段不参与多态!
    void sound() { System.out.println("汪汪!"); }
    void fetch() { System.out.println("捡球"); }
}

Animal a = new Dog();
a.sound();           // "汪汪!"        ← 方法看运行时
System.out.println(a.type); // "动物"  ← 字段看编译时(坑!)
// a.fetch();        // ❌ 编译错误,编译时类型是 Animal

⚠️ 重点方法多态、字段不多态!字段访问看声明类型。


6. 多态的四大经典用法

6.1 数组里装"一类东西"

java
Animal[] zoo = { new Dog(), new Cat(), new Cow() };
for (Animal a : zoo) a.sound();

6.2 方法参数

java
void feed(Animal a) {       // 接收任意 Animal 子类
    a.eat();
}

feed(new Dog());
feed(new Cat());

6.3 方法返回类型

java
Animal createAnimal(String type) {
    return switch (type) {
        case "dog" -> new Dog();
        case "cat" -> new Cat();
        default -> new Animal();
    };
}

6.4 集合里装"一类东西"

java
List<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());

7. 为什么多态如此重要?

不用多态的代码

java
void payAll(List<Order> orders) {
    for (Order o : orders) {
        if (o.type.equals("alipay")) { ... }
        else if (o.type.equals("wechat")) { ... }
        else if (o.type.equals("bank")) { ... }
        else if (o.type.equals("paypal")) { ... }
        // 加一种支付方式 → 改一遍
    }
}

用多态的代码

java
abstract class Payment {
    abstract void pay(double amount);
}
class AliPay   extends Payment { void pay(double a) { ... } }
class WeChatPay extends Payment { void pay(double a) { ... } }
class BankPay  extends Payment { void pay(double a) { ... } }

void payAll(List<Payment> payments) {
    for (Payment p : payments) p.pay(100);     // 调用方完全不变
}

加新支付方式只需新增类,不用改任何已有代码 —— 这就是多态的力量(开闭原则)!


8. 实战练习

文件内容
Animal.java父类
AnimalDemo.java多态综合演示
PaymentDemo.java多态实战:支付系统
CastDemo.java向上/向下转型与 instanceof

9. 浏览器演示

打开 demo.html 体验:

  • 动物园动态发声
  • 转型成功 / 失败现场
  • 字段不多态翻车

10. 面试可能会问什么?

Q1: 多态的实现原理?

:基于 虚方法表(vtable)。每个类有一张方法表,记录所有可被重写的方法的实际地址。父类引用调方法时,JVM 通过对象的真实类型查表,找到真实的方法。

Q2: 多态有什么好处?

  1. 可扩展性:新增子类不影响旧代码(开闭原则)
  2. 可替换性:不同子类可以互换使用
  3. 解耦:调用方依赖抽象,不依赖具体

Q3: 字段是多态的吗?

字段不是多态的。字段访问看 编译时类型(声明类型),不看运行时对象。 方法才是多态的(看运行时类型)。

Q4: static 方法能多态吗?

不能。static 方法属于类,不属于对象,不参与多态。子类用同名 static 方法叫"隐藏(hide)",不是重写。

Q5: 私有方法能被重写吗?

不能。private 方法对子类不可见,子类即使写了同名方法也是"全新的方法",不是重写。

Q6: 重写时为什么要加 @Override

  1. 编译期检查:拼错名字、参数错误时报错
  2. 代码可读性:明确告诉读者"这是重写"
  3. 重构友好:父类方法删了,所有 @Override 子类都报错

🎁 本章小结

✅ 多态:父类引用指向子类对象,调用看运行时
✅ 三前提:继承 + 重写 + 父类引用指向子类
✅ 向上转型自动安全;向下转型用 instanceof 防翻车
✅ 编译看左边(能调什么),运行看右边(实际调谁)
✅ 字段不多态,方法才多态

🔗 导航