优先用组合而非继承,因继承强制暴露父类实现细节、导致高耦合;组合通过接口依赖和构造注入实现显式可控的复用,更安全灵活。

继承会强制暴露父类实现细节
用 extends 时,子类自动获得父类所有非私有字段和方法,哪怕你只想要其中一两个功能。这导致耦合变高——父类一改,子类可能莫名其妙报错,比如 NullPointerException 或重写逻辑被意外跳过。
常见错误现象:父类加了个新 protected 字段,子类里同名变量引发遮蔽;或者父类重构了 init() 方法调用顺序,子类的 super.init() 放错位置直接初始化失败。
- 优先考虑是否真需要“是某种类型”(如
Dog extends Animal),而不是“能做某件事” - 如果父类不是为继承设计的(比如没写
final方法、没说明哪些方法可覆写),别轻易继承 - 留意
getClass().getSuperclass()返回的类是否真的符合你对“父类职责”的预期
组合让依赖关系显式且可控
用字段持有其他对象(比如 private DatabaseClient db;),就只暴露你主动调用的接口。别人改 DatabaseClient 内部,只要 query() 方法签名不变,你的类完全不受影响。
使用场景:工具类封装(JsonParser)、外部服务接入(PaymentGateway)、策略替换(SortStrategy)——这些都不该被“继承”,而应作为能力被注入。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 把依赖声明为接口类型(如
private DataSource dataSource;),而非具体实现 - 构造函数传入依赖,比在方法里 new 实例更利于测试和替换
- 避免在组合对象上调用链式方法(如
user.getProfile().getAddress().getCity()),容易引发NullPointerException
Java 8+ 接口默认方法模糊了边界
接口加了 default 方法后,看起来像“轻量继承”,但本质仍是契约,不是状态共享。它解决的是 API 演进问题,不是替代组合或继承的设计决策。
参数差异明显:继承能访问 protected 成员、能覆写 final 方法(不行)、能调用父类构造器;接口默认方法不能访问实现类的字段,也不能调用 super 构造器。
- 别用
default方法去模拟模板方法模式(那是抽象类的事) - 如果默认方法里要读写状态,说明这个行为本该属于某个具体类,而不是接口
- 多个接口提供同名
default方法时,实现类必须显式覆写,否则编译报错:class A inherits unrelated defaults for methodX() from types B and C
选组合还是继承?看能不能回答这三个问题
不用查资料,现场快速判断:
- 子类是否真的“是一种”父类?(
ArrayList是一种List,但OrderProcessor不是一种DatabaseConnection) - 父类有没有定义明确的、可复用的“模板逻辑”?(比如
AbstractList的add()调用add(int, E)) - 你是否需要访问父类的
protected字段或调用其构造器?如果不需要,大概率该用组合
最容易被忽略的是:继承常被用来“复用代码”,但 Java 的包级访问控制(package-private)会让这种复用在跨包时突然失效——而组合从一开始就把可见性控制在字段声明那一行。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










