主题
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: 多态有什么好处?
- 可扩展性:新增子类不影响旧代码(开闭原则)
- 可替换性:不同子类可以互换使用
- 解耦:调用方依赖抽象,不依赖具体
Q3: 字段是多态的吗?
答:字段不是多态的。字段访问看 编译时类型(声明类型),不看运行时对象。 方法才是多态的(看运行时类型)。
Q4: static 方法能多态吗?
答:不能。static 方法属于类,不属于对象,不参与多态。子类用同名 static 方法叫"隐藏(hide)",不是重写。
Q5: 私有方法能被重写吗?
答:不能。private 方法对子类不可见,子类即使写了同名方法也是"全新的方法",不是重写。
Q6: 重写时为什么要加 @Override?
答:
- 编译期检查:拼错名字、参数错误时报错
- 代码可读性:明确告诉读者"这是重写"
- 重构友好:父类方法删了,所有 @Override 子类都报错
🎁 本章小结
✅ 多态:父类引用指向子类对象,调用看运行时
✅ 三前提:继承 + 重写 + 父类引用指向子类
✅ 向上转型自动安全;向下转型用 instanceof 防翻车
✅ 编译看左边(能调什么),运行看右边(实际调谁)
✅ 字段不多态,方法才多态🔗 导航
- ⬅️ 上一章:Chapter 09 · 继承
- ➡️ 下一章:Chapter 11 · 抽象类与接口