应避免过度继承,优先使用组合+接口;继承易导致紧耦合、维护困难、方法歧义等问题,建议控制在2层以内,非设计为被继承的类加final,用protected明确契约。

过度继承导致子类紧耦合父类实现
父类一旦修改内部逻辑或字段,所有子类都可能意外失效。比如父类 Animal 原本用 String name 存名字,后来改成 Optional,所有直接读取 name 字段的子类(如 Dog、Cat)编译不过,甚至没报错但运行时 NullPointerException。
常见错误现象:
- 修改父类 private 字段名后,子类编译失败(因用了反射或 lombok 的
@Data) - 父类加了新构造函数,子类
super(...)调用断裂 - 父类重写了
equals()但没考虑子类新增字段,导致逻辑不一致
建议优先用组合:把可复用行为封装成独立类(如 NameHolder、SoundEmitter),让 Dog 持有它,而不是继承 Animal。
继承树过深引发方法调用歧义和维护困难
Java 不支持多继承,但若硬凑出 LivingThing → Animal → Mammal → Carnivore → Dog 这样的五层结构,问题就来了:
立即学习“Java免费学习笔记(深入)”;
-
toString()或getInfo()这类通用方法在每一层都被重写,调试时难以定位实际执行的是哪一版 - 新增一个
FlyingMammal分支时,要么复制粘贴逻辑,要么强行上提方法到Mammal层——后者常破坏单一职责 - IDE 提示“method is inherited from X”泛滥,关键逻辑被埋没
典型场景:Spring Boot 项目里有人定义 BaseController → AdminController → UserAdminController → UserPasswordResetController,结果改个分页参数就得顺次检查四层 @ModelAttribute 注解是否冲突。
更稳妥的做法是控制在 2 层以内(如仅 BaseEntity → User),深层共性提取为接口(Identifiable、Timestamped)或工具类。
final 方法和访问修饰符让继承变得脆弱
父类作者未必预料到继承场景。比如:
- 把
calculateScore()设为protected,但没加注释说明“该方法预期被子类覆盖”,结果子类重写后破坏了父类validate()的前置校验逻辑 - 父类某方法调用了未声明为
final的loadConfig(),子类覆写它返回空 map,导致整个初始化流程静默失败 -
public字段暴露(如public int version),子类直接修改,绕过父类的版本升级约束逻辑
实操建议:
- 非设计为被继承的类,加上
final关键字 - 可被继承的类,用
protected暴露方法前,明确文档说明契约(输入/输出/副作用) - 避免
public字段;字段一律private,通过protectedgetter/setter 控制访问
替代方案:组合 + 接口比继承更可控
不是不能用继承,而是多数业务场景中,组合更贴近真实关系。例如:
- “狗会吠叫”不是“狗是一种吠叫”,而是“狗持有吠叫能力”
- “订单支持退款”不意味着
RefundableOrder extends Order,而应是Order implements Refundable,再由RefundService处理逻辑
示例对比:
// ❌ 容易失控的继承
class EmailNotification extends Notification { ... }
class SmsNotification extends Notification { ... }
// 后来要加推送通知?得再建 PushNotification,且所有发送逻辑重复
// ✅ 更灵活的组合
interface Notifier {
void send(String content);
}
class EmailNotifier implements Notifier { ... }
class SmsNotifier implements Notifier { ... }
class OrderService {
private final Notifier notifier; // 运行时注入
}接口定义契约,组合赋予行为,两者配合能快速应对变化。而继承一旦成型,调整成本远高于重构字段或实现类。
真正难处理的,往往是那些早期被当作“理所当然可继承”的基类——它们没有明确边界,又散落着状态和逻辑,改一处牵全身。










