🎯 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 多态的三要素

🔑 构成多态的三个条件:

  1. 有继承关系:子类继承父类
  2. 有方法重写:子类重写父类方法
  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 方法调用原理

🔑 多态的调用原理:

  1. 编译时:编译器检查 a.cry() —— 父类 Animal 有 cry() 方法吗? → 编译通过
  2. 运行时: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

  • 每个 ifelse ifswitch caseforwhile&&|| 都算一个判定节点
  • 圈复杂度越高,代码越难理解、测试和维护

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 方法 ❌ 危险 可被子类重写,多态导致未初始化问题

最佳实践:构造方法中只调用 privatefinalstatic 方法


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 的精髓所在 —— 它让"统一调用"成为可能,让代码具备极强的扩展性。掌握多态,就掌握了写出优雅、通用、易扩展代码的钥匙。

理解多态的关键:编译看左边,运行看右边。记住这个核心规律,多态就不再神秘。

多态的下一个知识点是面向对象综合应用 —— 把封装、继承、多态三者结合,构造真正的面向对象系统。

如有问题或建议,欢迎在评论区留言交流! 🙌

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐