Java 继承与多态

继承、方法覆盖与动态分派:子类型如何替换父类型,多态如何在运行时选择实现。

#type / concept #status / growing #tech / dev #resource / java

[!info] 关联笔记

Java 继承与多态

这个概念为什么出现

业务常有“通用流程 + 特殊规则”:所有支付单要校验金额,但信用卡与钱包的扣款方式不同。继承与多态允许用统一父类型引用操作不同子类实现,把变化点留在覆盖方法里。

[!abstract] 一句话理解 子类继承父类可复用结构与行为;覆盖实例方法后,调用由运行时类型动态分派;编译期类型决定可见成员,运行时类型决定覆盖实现。

最小可运行示例

场景:支付网关用同一“结算”入口处理不同渠道

abstract class Payment {
    final int amountCents;
    Payment(int amountCents) {
        if (amountCents <= 0) throw new IllegalArgumentException("amount");
        this.amountCents = amountCents;
    }
    // 模板步骤
    final String settle() {
        return channel() + " settled=" + amountCents;
    }
    abstract String channel();
}

class CardPayment extends Payment {
    CardPayment(int amountCents) { super(amountCents); }
    @Override String channel() { return "CARD"; }
}

class WalletPayment extends Payment {
    WalletPayment(int amountCents) { super(amountCents); }
    @Override String channel() { return "WALLET"; }
}

public class PaymentRouter {
    // route:业务只依赖父类型。
    // 教学点:编译期类型 Payment,运行时分派 channel()。
    static String route(Payment p) {
        return p.settle();
    }
    public static void main(String[] args) {
        System.out.println(route(new CardPayment(100)));
        System.out.println(route(new WalletPayment(50)));
    }
}

建议运行:

javac PaymentRouter.java && java PaymentRouter

期望输出:

CARD settled=100
WALLET settled=50

结合场景再看三个关注点

  1. @Override 帮助编译器校验签名,避免“本想覆盖却重载”。
  2. 父类构造先于子类字段初始化
  3. 继承是强耦合;能用组合/接口时优先考虑。

核心概念与准确模型

1. 继承关系

  • extends 单继承类
  • 子类获得父类可见成员
  • super 调用父类构造/方法

2. 覆盖(override)条件(直觉)

  • 实例方法同签名
  • 返回类型协变允许
  • 访问权限不能更窄
  • static/私有/final 不能按实例多态覆盖语义使用

3. 向上转型与向下转型

Payment p = new CardPayment(1); // 向上
CardPayment c = (CardPayment) p; // 向下,失败则 ClassCastException

优先用多态方法,而不是到处强转。

4. 动态分派

  • 实例方法按运行时类型选实现
  • 字段访问与 static 方法不按同样多态规则“覆盖字段”

边界情况与反直觉行为

  1. 构造器中调用可覆盖方法 → 子类未初始化风险。
  2. 误把重载当覆盖。
  3. 深继承树使变更爆炸。

常见误区

[!warning] 常见误区:继承为了复用私有实现细节 复用优先组合;继承表达的是“是一种”且愿意遵守父类契约(LSP)。

工程实践

  1. 优先 final 类/方法锁住不该被扩的点。
  2. 父类型表达行为契约,而不是暴露一堆可写字段。
  3. 用接口定义能力,用小继承树实现。
  4. equals 与继承一起设计要特别小心(见 equals 篇)。

本节总结

  • 继承复用与特化;多态靠动态分派
  • 编译期类型 vs 运行时类型
  • 组合优于盲目继承

自测题

  1. 覆盖与重载如何区分?
  2. 为什么支付路由依赖 Payment 而不是具体类?
参考答案
  1. 覆盖:继承体系同签名实例方法替换;重载:同名不同参,编译期解析。
  2. 对扩展开放:新增渠道不必改路由签名,只新增子类型。

延伸阅读与资料来源

资料类型支撑内容
JLS Inheritance / Override规范继承与方法
Polymorphism (Oracle Tutorial)教程多态
创建于 2026/7/15 更新于 2026/7/15