源本科技 | 码上会

Java 中的多态

2025/12/27
39
0

学习目标

  • 理解多态的核心思想:“同一接口,多种实现”

  • 掌握 编译时多态(方法重载)运行时多态(方法重写) 的区别与应用

  • 能够设计支持动态行为的类体系结构

  • 理解多态如何提升代码的可扩展性、可维护性与抽象能力


什么是多态

多态(Polymorphism) 源自希腊语:

  • poly = many(多)

  • morph = forms(形态)

定义:在 Java 中,多态指同一个方法调用,在不同对象上表现出不同行为的能力。

现实类比:一个人的多重身份

  • 在公司 → 是“员工”

  • 在家里 → 是“父亲”

  • 在父母面前 → 是“儿子”

同一个人,根据上下文扮演不同角色,执行不同行为。

Person p = new Father();
p.role(); // 输出:"I am a father."

多态的两大类型

polymorphism_in_java.webp

Java 中的多态分为两类:

类型

别名

实现方式

决策时机

编译时多态

静态多态

方法重载(Overloading)

编译期

运行时多态

动态多态

方法重写(Overriding)

运行期


编译时多态:方法重载

定义

同一个类中,存在多个同名但参数列表不同的方法。

注意:仅返回类型不同 不算重载

重载的条件(满足其一即可)

  • 参数数量不同

  • 参数类型不同

  • 参数顺序不同(当类型不同时)

示例:计算器中的 multiply 方法

class Calculator {
    // 重载:整数相乘
    int multiply(int a, int b) {
        return a * b;
    }

    // 重载:浮点数相乘
    double multiply(double a, double b) {
        return a * b;
    }

    // 重载:三个整数相乘
    int multiply(int a, int b, int c) {
        return a * b * c;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.multiply(2, 3));       // 6
        System.out.println(calc.multiply(2.5, 3.0));   // 7.5
        System.out.println(calc.multiply(2, 3, 4));    // 24
    }
}

编译器在编译时根据参数类型和数量决定调用哪个方法。


运行时多态:方法重写

定义

子类重新定义父类中已有的方法,提供特定实现。

重写的规则(必须全部满足)

  • 方法名相同

  • 参数列表完全相同

  • 返回类型相同(或协变返回类型)

  • 访问权限不能更严格(如父类是 public,子类不能是 private

  • 不能重写 staticfinalprivate 方法

核心机制:动态方法分派

JVM 在运行时根据实际对象类型决定调用哪个方法。

示例:动物发声系统

// 父类
class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

// 子类
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows");
    }
}

// 主程序
public class Main {
    public static void main(String[] args) {
        Animal a;

        a = new Dog();
        a.sound(); // 输出:Dog barks

        a = new Cat();
        a.sound(); // 输出:Cat meows
    }
}

关键点:

  • 引用类型是 Animal

  • 实际对象是 DogCat

  • JVM 在运行时选择实际对象的重写方法


多态的三大核心特征

特征

说明

多种行为

同一方法在不同对象上有不同实现

方法重写

子类定制父类行为的基础

运行时决策

方法调用由 JVM 动态绑定


为什么使用多态

优势一览

优势

说明

代码复用

通过父类引用操作任意子类对象

灵活性高

新增子类无需修改现有调用逻辑

抽象能力强

可基于接口 / 抽象类编程,屏蔽细节

易于扩展

符合“开闭原则”(对扩展开放,对修改关闭)

实际应用场景

// 通用绘图系统
void drawShape(Shape s) {
    s.draw(); // 无需知道具体是 Circle 还是 Rectangle
}

// 调用
drawShape(new Circle());
drawShape(new Rectangle());

未来新增 Triangle 类,drawShape() 无需改动!


多态的潜在缺点

缺点

说明

应对建议

理解成本略高

初学者可能混淆引用类型与实际类型

多画内存图、多调试

微小性能开销

动态绑定需查虚方法表(vtable)

现代 JVM 优化极好,通常可忽略

调试复杂度增加

断点可能跳转到未知子类

使用 IDE 的“Step Into Selection”功能

结论:优点远大于缺点,是 OOP 的核心力量。


多态 vs 封装 vs 继承

原则

作用

与多态的关系

封装

隐藏内部状态,提供受控访问

为多态提供安全的数据基础

继承

实现代码复用和“is-a”关系

是运行时多态的前提

多态

实现“同一接口,多种行为”

依赖继承 + 方法重写实现

三者协同:继承提供结构,封装保障安全,多态赋予灵活行为。


最佳实践

  1. 始终使用 @Override 注解
    防止拼写错误或签名不匹配:

    @Override
    void sound() { ... }
  2. 优先面向接口 / 抽象类编程

    List<String> list = new ArrayList<>(); // 而非 ArrayList<String> list = ...
  3. 避免在构造器中调用可重写方法
    子类可能未初始化完成,导致空指针等异常。

  4. 合理使用多态,避免过度设计
    简单场景无需强行引入继承体系。


重点总结

  • 多态 = 同一操作,多种实现

  • 编译时多态:方法重载(Overloading)→ 参数不同

  • 运行时多态:方法重写(Overriding)→ 子类定制行为

  • 运行时多态依赖 继承 + 方法重写 + 父类引用指向子类对象

  • 多态是实现高内聚、低耦合、易扩展系统的关键

  • Java 不支持运算符重载(除 + 用于字符串拼接)


思考题

  1. 为什么方法重载发生在编译期,而方法重写发生在运行期?

  2. 如果父类方法是 private,子类定义同名方法,这算重写吗?为什么?

  3. 如何利用多态设计一个支持多种支付方式(微信、支付宝、银行卡)的系统?