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