继承的本质是建立is-a逻辑关系,而非复制代码;子类天然具备父类类型身份与行为,通过JVM运行时类型系统支持,而非代码拷贝。

继承的本质不是“复制代码”,而是建立 is-a 逻辑关系
很多人初学时把 extends 理解成“把父类代码拷一份到子类里”,这是典型误解。Java 继承真正做的是:让子类对象**天然具备父类的类型身份和可访问行为**。比如 Student extends Person,意味着“一个 Student 就是一个 Person”,所以能直接传给需要 Person 参数的方法,也能调用 Person 中的 eat()、sleep()——不是因为代码被粘贴了,而是 JVM 在运行时认可这种类型归属。
常见错误现象:
– 写了 class Dog extends Animal,却在 Dog 里又重新声明 private String name;
– 父类用 private 修饰字段,子类试图用 super.name 访问,编译报错 cannot find symbol。
- 判断是否该用继承,只看一句话:**“X 是一种 Y 吗?”**(如“猫是一种动物”✅,“汽车有一个发动机”❌ → 应该用组合)
- 父类中需被子类访问的成员,优先用
protected,而非private或默认包权限 - 构造方法不会被继承,但子类构造器第一行默认调用
super();若父类没无参构造器,子类必须显式写super(...)
为什么 Java 只允许单继承?它真会限制开发吗?
Java 强制一个类只能 extends 一个父类,这是语言设计上的主动取舍,不是技术缺陷。它避免了多继承带来的“菱形问题”(如两个父类都有同名方法,子类调用时歧义),也让类层次更清晰、IDE 推导更准确、JVM 类加载更高效。
但实际开发中几乎不会被卡住,因为:
– 接口(interface)支持多实现,可补足“能做什么”的能力声明;
– 组合(has-a)比继承更灵活,比如 Car 不继承 Engine,而是持有 Engine engine 字段。
立即学习“Java免费学习笔记(深入)”;
- 别为了“复用几个方法”硬凑继承关系,尤其是跨业务域的类(如让
Order去继承Log) - 当发现子类大量重写父类方法、或只用到父类 20% 的功能时,大概率该换成组合
-
final class无法被继承,这是明确的设计意图,不是 bug
子类怎么安全地修改父类行为?重写 ≠ 覆盖全部
重写(@Override)是继承中最易出错的环节。它不是推倒重来,而是在保留父类契约的前提下定制逻辑。比如父类 Animal.eat() 定义了“进食”这个动作语义,子类 Dog.eat() 可以细化为“啃骨头”,但不能改成“充电”或抛出 IOException(违反父类声明)。
- 必须加
@Override注解:不加不会报错,但拼错方法名或参数类型时,你以为重写了,其实只是定义了个新方法 - 返回类型可以是父类返回类型的子类(协变返回),如父类返回
Animal,子类可返回Dog -
private、static、final方法不能被重写;其中static方法在子类中同名定义,属于“隐藏(hiding)”,不是重写 - 如果父类方法抛出
SQLException,子类重写时只能抛更具体的异常(如SQLTimeoutException)或不抛
继承链过深会带来什么隐性成本?
三层以上继承(A → B → C → D)看似体现“抽象层次”,实则增加理解负担和维护风险。每次修改 A 类,所有下游子类都可能意外受影响;调试时栈帧里堆满 super.super.method(),定位困难;单元测试也得层层 mock 上级依赖。
真实项目中更常见的健康结构是:1 层抽象基类 + 多个平级具体子类(如 PaymentProcessor → AlipayProcessor、WechatProcessor),而非 PaymentProcessor → OnlinePayment → MobileOnlinePayment → QRCodeMobilePayment。
- 每新增一层继承,就多一个“必须遵守的隐式协议”,文档稍有缺失,后续人就容易踩坑
- 考虑用模板方法模式(Template Method Pattern)替代深层继承:把骨架逻辑放在父类
final方法中,钩子(hook)方法留给子类实现 - Java 8+ 后,接口 default 方法已能承担部分“默认行为”职责,减少对抽象父类的依赖
最常被忽略的一点:继承关系一旦发布(如打成 jar 包供他人使用),就几乎不可逆。改父类签名、删方法、调整 protected 成员,都可能让下游编译失败或运行时异常——所以设计父类时,要像签合同一样谨慎。








