【Java】多态 一文搞定OOP的精髓
🎯 Java 多态详解
📌 多态是 Java 面向对象三大特性之一(封装、继承、多态)
理解多态,就理解了 Java 面向对象编程的终极威力
📖 本文导读
本文系统讲解 Java 多态的概念、三种表现形式、向上/向下转型、instanceof、抽象类与接口的多态应用、内存模型、优势与注意事项。适合已掌握继承和方法重写的读者
文章目录
1️⃣ 多态的概念 🎯
1.1 先看一个场景
public class Animal {
public void cry() {
System.out.println("动物发出叫声");
}
}
public class Dog extends Animal {
@Override
public void cry() {
System.out.println("汪汪汪");
}
}
public class Cat extends Animal {
@Override
public void cry() {
System.out.println("喵喵喵");
}
}
public class Pig extends Animal {
@Override
public void cry() {
System.out.println("哼哼哼");
}
}
现在有一个需求:写一个方法,接收任意动物,发出它的叫声。
没有多态时:
public class Test {
public static void cryDog(Dog d) { d.cry(); }
public static void cryCat(Cat c) { c.cry(); }
public static void cryPig(Pig p) { p.cry(); }
// 每增加一种动物就要写一个新方法,累死!
}
有多态时(一行搞定):
public class Test {
public static void animalCry(Animal a) {
a.cry(); // 传入什么动物,就叫什么声音
}
public static void main(String[] args) {
animalCry(new Dog()); // 输出:汪汪汪
animalCry(new Cat()); // 输出:喵喵喵
animalCry(new Pig()); // 输出:哼哼哼
}
}
💡 同一个调用语句
a.cry(),产生了完全不同的行为 —— 这就是多态
1.2 什么是多态?
多态(Polymorphism):同一类型的引用,在指向不同类型的对象时,执行同一个方法,表现出不同的行为
- 编译时多态:方法重载(Overload)—— 编译时静态绑定
- 运行时多态:方法重写(Override)—— 运行时动态绑定,我们说的"多态"通常指这个
Animal a = new Dog(); // 编译类型是 Animal,运行类型是 Dog
a.cry(); // 运行时调用的是 Dog 的 cry()
1.3 多态的三要素
🔑 构成多态的三个条件:
- 有继承关系:子类继承父类
- 有方法重写:子类重写父类方法
- 有父类引用指向子类对象:
父类类型 引用 = new 子类类型();
// 三要素缺一不可
public class Test {
public static void main(String[] args) {
// ✅ 完整三要素:继承 + 重写 + 父类引用指向子类对象
Animal a = new Dog(); // 运行时多态
a.cry(); // 输出:汪汪汪
// ❌ 没有重写:不构成多态(调用的是 Animal 自己的 cry)
Animal b = new Animal();
b.cry(); // 输出:动物发出叫声
}
}
2️⃣ 多态的语法 —— 向上转型 😎
2.1 什么是向上转型?
向上转型(Upcasting):把子类对象赋值给父类引用 → 自动转型,安全
父类类型 引用 = new 子类类型();
// 示例
Animal a = new Dog(); // ✅ 自动向上转型:Dog → Animal
Animal b = new Cat(); // ✅ 自动向上转型:Cat → Animal
Animal c = new Pig(); // ✅ 自动向上转型:Pig → Animal
💡 "向上"指的是继承树的层级 —— 子类在下面,父类在上面,所以把子类对象赋给父类引用叫"向上"转型
向上转型是自动的、安全的,不需要强制转换
2.2 向上转型的特点
| 特点 | 说明 |
|---|---|
| 自动转型 | 子类 → 父类,自动完成,无需强制类型转换 |
| 编译类型 | 父类类型(决定了能调用哪些成员) |
| 运行类型 | 子类类型(决定了实际执行哪个方法) |
| 能访问的成员 | 只限于父类中定义的成员(编译时确定) |
public class Animal {
String name = "动物";
public void eat() {
System.out.println("动物在吃东西");
}
public void cry() {
System.out.println("动物发出叫声");
}
}
public class Dog extends Animal {
String name = "狗"; // 重名字段(不是重写,是隐藏)
@Override
public void cry() {
System.out.println("汪汪汪");
}
public void run() {
System.out.println("狗在奔跑");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); // 向上转型
// 成员变量:编译类型决定,取父类的值
System.out.println(a.name); // 输出:动物(不是"狗"!)
// 成员方法(已重写):运行类型决定,调用子类版本
a.cry(); // 输出:汪汪汪(多态!)
// 成员方法(未重写):编译类型决定,调用父类版本
a.eat(); // 输出:动物在吃东西
// ❌ 编译错误:run() 是 Dog 独有方法,父类引用看不到
// a.run();
}
}
🔑 多态的核心规律:
- 成员变量:看编译类型(左边),不涉及多态
- 成员方法:优先调用运行类型的版本(重写方法),体现多态
- 非重写方法:看编译类型(左边)
2.3 为什么要向上转型?
向上转型的核心价值:统一调用接口,让代码更通用:
// 不用多态:每个方法参数不同,代码冗余
public class Vet {
public void treat(Dog d) { d.cry(); }
public void treat(Cat c) { c.cry(); }
public void treat(Pig p) { p.cry(); }
}
// 用多态:一种方法,处理所有动物
public class Vet {
public void treat(Animal a) { // 父类引用可以接收任意子类对象
a.cry(); // 运行时自动调用真实类型的cry()
}
}
public class Test {
public static void main(String[] args) {
Vet vet = new Vet();
vet.treat(new Dog()); // 输出:汪汪汪
vet.treat(new Cat()); // 输出:喵喵喵
vet.treat(new Pig()); // 输出:哼哼哼
}
}
3️⃣ 向下转型 😎
3.1 什么是向下转型?
向下转型:把父类引用强制转换为子类引用 → 必须手动强转,有风险
父类类型 引用 = new 子类类型(); // 向上转型
子类类型 新引用 = (子类类型) 引用; // 向下转型(强制)
// 示例
Animal a = new Dog(); // 向上转型:Dog → Animal
Dog d = (Dog) a; // 向下转型:Animal → Dog
d.run(); // 现在可以调用 Dog 独有的方法了!
3.2 为什么需要向下转型?
向上转型后,父类引用看不到子类独有的成员。如果确实需要访问子类独有的方法,就需要向下转型:
public class Animal {
public void cry() { }
}
public class Dog extends Animal {
public void run() {
System.out.println("狗在奔跑");
}
}
public class Test {
public static void main(String[] args) {
// 向上转型:失去了 Dog 的独有方法
Animal a = new Dog();
// a.run(); // ❌ 编译错误!Animal 没有 run()
// 向下转型:恢复子类类型,可以访问独有成员
Dog d = (Dog) a;
d.run(); // ✅ 输出:狗在奔跑
// 也可以链式写法
((Dog) a).run(); // ✅ 一步到位
}
}
3.3 向下转型的风险 —— ClassCastException
向下转型存在风险:如果父类引用指向的不是目标子类,会抛出 ClassCastException(类型转换异常):
Animal a = new Cat(); // a 实际上是 Cat
Dog d = (Dog) a; // ❌ 编译通过!运行时 ClassCastException
// 猫怎么能转成狗?
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
3.4 安全转型:instanceof
在向下转型之前,必须先判断对象的真实类型,避免 ClassCastException。
引用 instanceof 类名 → 判断引用指向的对象**是否是这个类(或其子类)**的实例:
Animal a1 = new Dog();
Animal a2 = new Cat();
System.out.println(a1 instanceof Dog); // true
System.out.println(a1 instanceof Cat); // false
System.out.println(a2 instanceof Dog); // false
System.out.println(a2 instanceof Cat); // true
System.out.println(a1 instanceof Animal); // true(所有类都是Animal的实例)
安全转型写法:
public class Test {
public static void treat(Animal a) {
a.cry(); // 所有动物都叫,多态
// 需要调用子类独有方法时,先判断再转型
if (a instanceof Dog) {
Dog d = (Dog) a; // 安全转型
d.run();
} else if (a instanceof Cat) {
Cat c = (Cat) a;
c.climb();
}
}
}
🔑 向下转型原则:先
instanceof再转型if (a instanceof Dog) { Dog d = (Dog) a; // 安全使用 d }
3.5 instanceof 的改进(Java 14+ Pattern Matching)
Java 14 引入了 instanceof 的模式匹配语法,可以一步完成判断+转型:
// 传统写法
if (a instanceof Dog) {
Dog d = (Dog) a; // 要手动转型
d.run();
}
// Java 14+ 模式匹配写法
if (a instanceof Dog d) { // 判断+转型一步完成
d.run(); // 直接使用 d,无需再转型
}
public class Test {
public static void treat(Animal a) {
if (a instanceof Dog d) {
d.cry(); // 狗叫
d.run(); // 狗跑
} else if (a instanceof Cat c) {
c.cry(); // 猫叫
c.climb(); // 猫爬树
} else if (a instanceof Pig p) {
p.cry(); // 猪叫
}
}
}
4️⃣ 多态的内存模型 😲
4.1 内存图解

4.2 方法调用原理
🔑 多态的调用原理:
- 编译时:编译器检查
a.cry()—— 父类 Animal 有cry()方法吗?有 → 编译通过- 运行时:JVM 根据
a指向的实际对象(Dog)→ 在 Dog 的方法区中查找cry()→ 找到 → 执行 Dog 的版本这就是运行时动态绑定(Late Binding)
4.3 多态与非多态的内存对比
Animal a = new Dog(); // 多态
a.cry(); // 运行时决定,调用 Dog.cry()
Dog d = new Dog(); // 非多态
d.cry(); // 编译时就知道,Dog.cry()
| 对比 | 多态(父类引用) | 非多态(子类引用) |
|---|---|---|
| 编译类型 | Animal | Dog |
| 运行类型 | Dog | Dog |
| 方法调用 | 运行时查找(动态绑定) | 编译时确定 |
| 能访问的成员 | 父类成员(+ 重写方法) | 父类 + 子类所有成员 |
5️⃣ 多态的实际应用 🛠️
5.1 场景一:参数多态(最常用)
把父类类型作为方法参数,实现通用方法:
// 需求:统计所有动物的总重量
public class Animal {
int weight;
public int getWeight() { return weight; }
}
public class Dog extends Animal {
public Dog(int w) { this.weight = w; }
}
public class Fish extends Animal {
public Fish(int w) { this.weight = w; }
}
// ✅ 多态写法:一个方法处理所有动物
public class Zoo {
public int totalWeight(Animal[] animals) {
int sum = 0;
for (Animal a : animals) {
sum += a.getWeight(); // 传入什么动物,就用它的体重
}
return sum;
}
}
// ❌ 不用多态的写法
// public int totalWeightDog(Dog[] dogs) { ... }
// public int totalWeightFish(Fish[] fish) { ... }
5.2 场景二:返回值多态
方法返回父类类型,实际返回子类对象:
public class Animal { }
public class Dog extends Animal { }
public class Cat extends Animal { }
public class AnimalFactory {
// 返回类型是 Animal,实际可以返回任意子类
public Animal create(String type) {
switch (type) {
case "dog": return new Dog(); // ✅ 实际返回 Dog
case "cat": return new Cat(); // ✅ 实际返回 Cat
default: return new Animal();
}
}
}
5.3 场景三:接口多态
接口 + 多态 是实际开发中最经典的应用:
interface Usb {
void connect();
}
class Mouse implements Usb {
@Override
public void connect() {
System.out.println("鼠标连接成功");
}
}
class Keyboard implements Usb {
@Override
public void connect() {
System.out.println("键盘连接成功");
}
}
// 一个方法,处理所有 USB 设备
public class Computer {
public void plugIn(Usb usb) {
usb.connect(); // 插入什么设备,就调用什么connect()
}
}
public class Test {
public static void main(String[] args) {
Computer c = new Computer();
c.plugIn(new Mouse()); // 输出:鼠标连接成功
c.plugIn(new Keyboard()); // 输出:键盘连接成功
}
}
6️⃣ 多态的优缺点 ⚖️
6.1 优点 ✅
| 优点 | 说明 |
|---|---|
| 代码复用 | 一个方法可以处理多种类型,减少重复代码 |
| 可扩展性强 | 新增子类无需修改已有代码,符合开闭原则 |
| 解耦合 | 调用方不需要知道具体子类类型,降低耦合 |
| 接口统一 | 统一父类/接口引用,隐藏实现差异 |
| 降低圈复杂度 | 消除大量 if-else / switch-case 分支判断 |
6.2 缺点 ❌
| 缺点 | 说明 |
|---|---|
| 运行效率略低 | 动态绑定需要在运行时查找方法表,比静态绑定稍慢(通常可忽略) |
| 调试困难 | 运行时才知道调用哪个方法,跟踪调用链不如静态调用直观 |
| 类型安全弱化 | 向上转型后只能访问父类成员,需要向下转型时存在 ClassCastException 风险 |
| 设计复杂度增加 | 需要合理设计继承层次和抽象层次,设计不当会导致类层次混乱 |
| 成员变量无多态 | 只能访问父类的成员变量,容易产生误解 |
6.3 优缺点权衡
💡 何时使用多态?
场景 建议 需要统一处理多种子类对象 ✅ 强烈推荐 频繁新增子类,不想改老代码 ✅ 强烈推荐 性能极度敏感的热点代码 ⚠️ 谨慎,考虑是否值得 代码可读性优先 ⚠️ 适度使用,避免过度抽象 成员变量需要多态访问 ❌ 不适用,成员变量无多态
7️⃣ 多态与圈复杂度 📉
7.1 什么是圈复杂度?
圈复杂度(Cyclomatic Complexity):衡量代码复杂度的指标,表示程序中独立路径的数量。
计算公式:圈复杂度 = 判定节点数 + 1
- 每个
if、else if、switch case、for、while、&&、||都算一个判定节点 - 圈复杂度越高,代码越难理解、测试和维护
7.2 不用多态:圈复杂度爆炸
// ❌ 不用多态:每新增一种动物,圈复杂度 +1
public void animalCry(Animal a) {
if (a instanceof Dog) {
System.out.println("汪汪汪");
} else if (a instanceof Cat) {
System.out.println("喵喵喵");
} else if (a instanceof Pig) {
System.out.println("哼哼哼");
} else if (a instanceof Cow) {
System.out.println("哞哞哞");
} else if (a instanceof Sheep) {
System.out.println("咩咩咩");
}
// 圈复杂度 = 5 + 1 = 6
// 新增 10 种动物 → 圈复杂度 = 15!
}
7.3 用多态:圈复杂度恒定为 1
// ✅ 用多态:无论多少种动物,圈复杂度始终为 1
public void animalCry(Animal a) {
a.cry(); // 一行搞定,圈复杂度 = 1
}
7.4 圈复杂度对比
| 写法 | 动物种类数 | 圈复杂度 | 新增动物时 |
|---|---|---|---|
| if-else 分支 | N | N + 1 | 修改方法,圈复杂度 +1 |
| 多态 | N | 1 | 无需修改,圈复杂度不变 |
🔑 多态是降低圈复杂度的利器!
- 消除
instanceof+if-else分支- 消除
switch-case分支- 让代码从"过程式分支"变成"面向对象分发"
- 测试用例数量大幅减少(测试用例数 ≈ 圈复杂度)
7.5 实战示例:消除 instanceof 分支
重构前(圈复杂度高):
public double calculateArea(Object shape) {
if (shape instanceof Circle c) {
return Math.PI * c.getRadius() * c.getRadius();
} else if (shape instanceof Rectangle r) {
return r.getWidth() * r.getHeight();
} else if (shape instanceof Triangle t) {
return 0.5 * t.getBase() * t.getHeight();
}
return 0;
}
// 圈复杂度 = 4
重构后(圈复杂度低):
interface Shape {
double area();
}
class Circle implements Shape {
double radius;
@Override
public double area() { return Math.PI * radius * radius; }
}
class Rectangle implements Shape {
double width, height;
@Override
public double area() { return width * height; }
}
class Triangle implements Shape {
double base, height;
@Override
public double area() { return 0.5 * base * height; }
}
// 调用方
public double calculateArea(Shape shape) {
return shape.area(); // 圈复杂度 = 1
}
8️⃣ 避免在构造方法中调用重写方法 🚫
8.1 问题场景
在父类构造方法中调用一个可被重写的方法,会导致严重问题:
public class Father {
protected int value = 100;
public Father() {
show(); // ❌ 危险!调用了可能被重写的方法
}
public void show() {
System.out.println("Father.show(), value = " + value);
}
}
public class Son extends Father {
private int sonValue = 200;
public Son() {
super(); // 先调用父类构造
// 父类构造中调用 show(),但此时 Son 的成员还未初始化!
}
@Override
public void show() {
// 此时 sonValue 还未初始化,值为默认值 0!
System.out.println("Son.show(), sonValue = " + sonValue);
}
}
public class Test {
public static void main(String[] args) {
new Son();
}
}
/* 输出:
Son.show(), sonValue = 0 ← 不是 200!因为 Son 的构造还没执行完
*/
8.2 问题分析
执行顺序:
1. new Son() 调用 Son 构造
2. Son 构造隐式调用 super() → Father 构造
3. Father 构造中调用 show()
4. 由于多态,实际调用的是 Son.show() ← 问题发生点!
5. 但此时 Son 的成员变量 sonValue 还未初始化(值为 0)
6. Father 构造执行完毕
7. Son 的成员变量初始化(sonValue = 200)
8. Son 构造方法体执行
⚠️ 核心问题:父类构造执行时,子类部分还未初始化,但多态已经生效!
- 子类重写方法可能依赖子类成员变量
- 此时子类成员变量还是默认值(0 / null / false)
- 导致程序行为异常,难以调试
8.3 正确做法
方案一:构造方法中只调用 private 或 final 方法
public class Father {
protected int value = 100;
public Father() {
init(); // ✅ 安全:init 是 private,不能被重写
}
private void init() { // private 方法不能被重写
System.out.println("Father.init(), value = " + value);
}
}
public class Son extends Father {
private int sonValue = 200;
public Son() {
super();
}
// 无法重写 Father 的 init(),因为它是 private
}
方案二:将方法标记为 final
public class Father {
protected int value = 100;
public Father() {
show(); // ✅ 安全:show 是 final,不能被重写
}
public final void show() { // final 方法不能被重写
System.out.println("Father.show(), value = " + value);
}
}
方案三:延迟调用,构造完成后由调用方主动触发
public class Father {
protected int value = 100;
public Father() {
// 构造中不做任何可能被重写的调用
}
public void init() { // 提供一个初始化方法,构造后调用
show();
}
public void show() {
System.out.println("Father.show(), value = " + value);
}
}
public class Son extends Father {
private int sonValue = 200;
public Son() {
super();
// sonValue 在此处已初始化
}
@Override
public void show() {
System.out.println("Son.show(), sonValue = " + sonValue); // 正确:200
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son();
s.init(); // ✅ 构造完成后再调用初始化方法
}
}
8.4 总结
🔑 构造方法中调用方法的原则:
方法类型 是否安全 原因 private方法✅ 安全 不能被重写,无多态问题 final方法✅ 安全 不能被重写,无多态问题 static方法✅ 安全 属于类,不参与多态 普通 public/protected方法❌ 危险 可被子类重写,多态导致未初始化问题 最佳实践:构造方法中只调用
private、final或static方法
9️⃣ 抽象类与接口在多态中的角色 🧐
9.1 抽象类多态
抽象类天生就是为多态准备的 —— 不能实例化,必须由子类继承并重写:
// 抽象类:定义规范,不提供实现
public abstract class Shape {
protected String color = "红色";
// 抽象方法:子类必须重写
public abstract double area();
// 具体方法:子类可以直接继承
public void printColor() {
System.out.println("颜色:" + color);
}
}
public class Circle extends Shape {
double r;
public Circle(double r) { this.r = r; }
@Override
public double area() {
return Math.PI * r * r;
}
}
public class Rectangle extends Shape {
double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }
@Override
public double area() {
return width * height;
}
}
// 多态应用
public class AreaCalculator {
public double sumArea(Shape[] shapes) {
double total = 0;
for (Shape s : shapes) {
total += s.area(); // 多态:每种图形调用自己的area()
}
return total;
}
}
9.2 接口多态
接口是多态最纯粹的实现方式 —— 定义"做什么",不关心"谁来做":
// 支付接口:定义支付行为规范
interface Payment {
void pay(double amount);
}
// 支付宝实现
class Alipay implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
}
}
// 微信支付实现
class WechatPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount + "元");
}
}
// 订单服务 —— 依赖接口,不依赖具体实现
public class OrderService {
public void checkout(Payment payment, double amount) {
// 传入什么支付方式,就用什么方式付款
payment.pay(amount);
}
}
public class Test {
public static void main(String[] args) {
OrderService order = new OrderService();
order.checkout(new Alipay(), 99.9); // 支付宝支付
order.checkout(new WechatPay(), 99.9); // 微信支付
}
}
9.3 抽象类 vs 接口在多态中的选择
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 关键词 | abstract class |
interface |
| 继承/实现 | extends(单继承) |
implements(可多实现) |
| 成员 | 可以有抽象/具体/构造方法 | 只能有抽象方法(Java 8+ 可 default) |
| 成员变量 | 可以有各种类型 | 只能是 public static final 常量 |
| 适用场景 | “是-a”(is-a)强分类关系 | “能-a”(can-do)能力扩展 |
| 多态用法 | 父类引用指向子类对象 | 接口引用指向实现类对象 |
🔟 多态的注意事项 ⚠️
10.1 不能被多态调用的成员
| 限制 | 原因 | 示例 |
|---|---|---|
| 子类独有的方法 | 编译类型是父类,父类中没有该方法 | a.run() ❌(Dog 独有) |
| 子类独有的成员变量 | 成员变量没有多态特性,看编译类型 | a.name → 输出父类的值 |
| static 方法 | static 方法属于类,不参与多态 | a.staticMethod() → 调父类版本 |
| private 方法 | private 方法对子类不可见,不存在重写 | — |
public class Father {
public void normal() { System.out.println("父类普通方法"); }
public static void staticMethod() { System.out.println("父类静态方法"); }
private void privateMethod() { System.out.println("父类私有方法"); }
}
public class Son extends Father {
@Override
public void normal() { System.out.println("子类重写方法"); }
public void unique() { System.out.println("子类独有方法"); }
}
public class Test {
public static void main(String[] args) {
Father f = new Son();
f.normal(); // ✅ 多态:输出"子类重写方法"
f.staticMethod(); // ❌ static:输出"父类静态方法"(static 不参与多态)
// f.unique(); // ❌ 编译错误:Father 没有 unique()
// f.privateMethod(); // ❌ 编译错误:private 对子类不可见
}
}
10.2 成员变量的多态特性
成员变量没有多态! 成员变量的访问只看编译类型,不看运行类型:
public class Father {
int num = 100;
}
public class Son extends Father {
int num = 200; // 这是变量隐藏,不是方法重写
}
public class Test {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.num); // 输出:100(不是 200!)
}
}
⚠️ 记住:只有实例方法参与多态,成员变量和 static 方法都不参与!
成员类型 访问依据 说明 实例方法 运行类型(动态绑定) ✅ 多态 成员变量 编译类型(静态绑定) ❌ 不多态 static 方法 编译类型(属于类) ❌ 不多态
10.3 多态的常见错误
错误一:认为多态就是子类引用指向父类对象
// ❌ 这是错的!父类对象不能用子类引用来指向
// Son s = new Animal(); // 编译错误!父类对象不是完整的子类对象
// ✅ 正确:子类对象赋给父类引用(向上转型)
Animal a = new Dog();
错误二:混淆重写与方法隐藏
public class Father {
public void cry() { System.out.println("父类"); }
}
public class Son extends Father {
// ❌ 这叫重写(Override):方法签名完全相同
@Override
public void cry() { System.out.println("子类"); }
// ❌ 这叫隐藏(Shadowing):父类的 cry() 仍然存在
// 但子类对象调 cry() 时走的是子类的重写版本
}
错误三:忘记 instanceof 判断就向下转型
Animal a = new Cat();
Dog d = (Dog) a; // ❌ ClassCastException!猫转不了狗
1️⃣1️⃣ 完整示例:支付系统 🏦
// 支付接口
interface Payment {
void pay(double amount);
void refund(double amount);
}
// 支付宝
class Alipay implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝收款:" + amount);
}
@Override
public void refund(double amount) {
System.out.println("支付宝退款:" + amount);
}
}
// 微信支付
class WechatPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信收款:" + amount);
}
@Override
public void refund(double amount) {
System.out.println("微信退款:" + amount);
}
}
// 银联支付
class UnionPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("银联收款:" + amount);
}
@Override
public void refund(double amount) {
System.out.println("银联退款:" + amount);
}
}
// 订单服务(多态的核心受益者)
public class OrderService {
// ✅ 一个方法处理所有支付方式
public void pay(Order order, Payment payment) {
payment.pay(order.getPrice()); // 多态!
}
// ✅ 一个方法处理所有退款方式
public void refund(Order order, Payment payment) {
payment.refund(order.getPrice()); // 多态!
}
}
// 新增支付方式?只需要写一个新类实现 Payment 接口
// OrderService 无需任何修改!✅
1️⃣2️⃣ 多态与设计模式 🎨
12.1 策略模式
多态是策略模式的基础:
// 策略接口
interface SortStrategy {
void sort(int[] arr);
}
// 快速排序策略
class QuickSort implements SortStrategy {
@Override
public void sort(int[] arr) {
System.out.println("使用快速排序");
// 快速排序实现...
}
}
// 归并排序策略
class MergeSort implements SortStrategy {
@Override
public void sort(int[] arr) {
System.out.println("使用归并排序");
// 归并排序实现...
}
}
// 上下文:持有策略引用,运行时可切换
public class Sorter {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] arr) {
strategy.sort(arr); // 多态:传入什么策略,就用什么算法
}
}
public class Test {
public static void main(String[] args) {
int[] arr = {5, 2, 8, 1};
Sorter sorter = new Sorter();
sorter.setStrategy(new QuickSort()); // 运行时切换策略
sorter.sort(arr); // 快速排序
sorter.setStrategy(new MergeSort()); // 运行时切换策略
sorter.sort(arr); // 归并排序
}
}
12.2 工厂模式 + 多态
interface Fruit {
void eat();
}
class Apple implements Fruit {
@Override
public void eat() { System.out.println("吃苹果"); }
}
class Banana implements Fruit {
@Override
public void eat() { System.out.println("吃香蕉"); }
}
// 工厂:根据名称创建水果
class FruitFactory {
public Fruit create(String name) {
switch (name) {
case "apple": return new Apple();
case "banana": return new Banana();
default: return null;
}
}
}
public class Test {
public static void main(String[] args) {
FruitFactory factory = new FruitFactory();
Fruit f1 = factory.create("apple"); // 向上转型
Fruit f2 = factory.create("banana"); // 向上转型
f1.eat(); // 多态:输出"吃苹果"
f2.eat(); // 多态:输出"吃香蕉"
}
}
1️⃣3️⃣ 多态与类型转换全总结 📊
13.1 转型速查表
| 转型方向 | 语法 | 自动/强制 | 安全性 | 是否需要 instanceof |
|---|---|---|---|---|
| 向上转型 | 父类 引用 = new 子类(); |
自动 | ✅ 安全 | 不需要 |
| 向下转型 | 子类 引用 = (子类) 父类引用; |
强制 | ❌ 有风险 | 必须先判断 |
13.2 转型口诀
📌 父类引用转子类,instanceof 先判断
📌 子类引用转父类,自动转型不用管
📊 小结
| 知识点 | 核心内容 |
|---|---|
| 多态概念 | 同一引用指向不同对象,执行同一方法,表现出不同行为 |
| 三要素 | 继承/重写 + 父类引用指向子类对象 |
| 向上转型 | 父类 引用 = new 子类(); 自动安全 |
| 向下转型 | (子类) 父类引用; 强制,有风险,需 instanceof 判断 |
| instanceof | 判断对象真实类型,安全转型 |
| 多态调用规律 | 实例方法→运行类型;成员变量/static→编译类型 |
| 优点 | 代码复用、可扩展、解耦合、降低圈复杂度 |
| 缺点 | 运行效率略低、调试困难、类型安全弱化 |
| 圈复杂度 | 多态可消除 if-else/switch 分支,圈复杂度恒为 1 |
| 构造方法陷阱 | 避免在构造中调用可重写方法(子类成员未初始化) |
| 抽象类多态 | 父类引用指向子类对象,调用子类重写方法 |
| 接口多态 | 接口引用指向实现类对象,实际开发最常用 |
| 经典应用 | 参数多态、返回值多态、接口多态 |
| 设计模式 | 策略模式、工厂模式都依赖多态 |
| 不参与多态 | 成员变量、static 方法、private 方法 |
✍️ 写在最后
多态是 Java OOP 的精髓所在 —— 它让"统一调用"成为可能,让代码具备极强的扩展性。掌握多态,就掌握了写出优雅、通用、易扩展代码的钥匙。
理解多态的关键:编译看左边,运行看右边。记住这个核心规律,多态就不再神秘。
多态的下一个知识点是面向对象综合应用 —— 把封装、继承、多态三者结合,构造真正的面向对象系统。
如有问题或建议,欢迎在评论区留言交流! 🙌
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)