extends是Java中实现单继承的强制语法,确立编译期确定的父子关系,支持多态但不继承private成员和构造器,重写需遵守访问权限、异常类型等规则,字段同名则隐藏而非覆盖,组合常比继承更灵活。

extends 是 Java 里继承的唯一语法,不是“可选方案”
Java 不支持多重继承,extends 是子类明确声明“我继承自谁”的强制语法。它不是装饰或配置,而是编译期就确定的父子关系绑定——一旦写上 class Child extends Parent,子类就自动获得父类的非私有成员(public/protected 字段和方法),且无法在运行时更改父类。
常见错误是以为 extends 只是“复制代码”,其实它复用的是行为契约:子类对象能被当作父类类型使用(多态基础),而不仅仅是拿几个方法来用。
- 父类的
private成员不会被继承,子类访问会编译报错error: cannot find symbol - 构造器不会被继承,子类必须显式调用
super()(哪怕无参)来初始化父类部分,否则编译失败 - 如果父类只有带参构造器,子类必须写带参构造器并手动调用
super(...),否则默认super()会找不到匹配方法
子类如何安全复用父类方法:重写 vs 直接调用
复用不等于照搬。父类方法在子类中可能需要调整逻辑,这时得区分场景:
- 想保留父类逻辑,再追加操作 → 在子类方法里用
super.methodName()显式调用,比如日志记录后调用父类保存逻辑 - 想替换整个逻辑 → 用
@Override重写,但注意:父类方法必须是public或protected;若父类方法是final,重写会编译报错error: method xxx in class Y cannot be overridden - 父类方法抛出检查异常(
IOException等),子类重写时不能抛出更宽泛的异常类型,否则编译失败
示例:父类有 public void start() { System.out.println("engine on"); },子类想加校验:
@Override
public void start() {
if (fuelLevel > 0) {
super.start(); // 复用原逻辑
System.out.println("car moving");
}
}
字段继承的陷阱:同名变量会隐藏,不是覆盖
如果子类定义了和父类同名的字段(比如都叫 name),子类的 name 会**隐藏**父类的 name,而不是覆盖。访问时取决于引用类型:
- 用子类引用访问
this.name→ 拿到子类字段值 - 用父类引用指向子类实例,再访问
parentRef.name→ 拿到父类字段值(字段不具多态性) - 想强制访问父类字段?只能用
super.name,但前提是父类字段不是private
这和方法重写完全不同——方法看实际类型,字段看声明类型。容易误以为“改了子类字段,父类逻辑也跟着变”,结果发现父类方法里用的还是自己的 name。
当 extends 不够用:组合优先于继承的现实考量
继承复用的前提是“is-a”关系成立(比如 Dog extends Animal)。但很多场景只是“has-a”或“uses-a”,硬套 extends 会导致设计僵化:
- 父类加个新方法,所有子类自动继承——哪怕它们根本不需要,还可能破坏封装
- 子类无法选择性继承,也不能继承多个父类行为(Java 不支持)
- 测试困难:父类逻辑变更可能意外影响几十个子类
这时候更稳妥的做法是把复用逻辑抽成独立类,子类通过字段持有它(组合),再调用其方法。比如网络请求逻辑,别让 OrderService 和 UserService 都 extends HttpBase,而是各自持有一个 HttpClient 实例。
继承不是代码复用的万能钥匙,它是强耦合的结构约束。真正容易被忽略的,是每次写 extends 前,该先问一句:这个子类真的“是”父类的一种,还是只是“用”到了它的某些能力?

















