Java中优先选组合而非继承,核心依据语义关系:is-a才考虑继承且须满足里氏替换与父类可继承性,has-a/uses-a则用组合,通过接口依赖与行为委托实现松耦合、易测试、可替换。

Java 中该用继承还是组合,核心看语义关系是否成立:是“某种类型”就考虑继承,是“拥有某个部件”就选组合。组合是默认推荐方式,继承需满足多个前提才安全。
先问一句:它真的是“一种”吗?
这是最直接的判断起点。比如 Dog 是 Animal、ArrayList 是 List,这种明确的 is-a 关系,继承在语义上站得住脚。但若你写 Car extends Engine 或 Order extends PaymentProcessor,逻辑就反了——车不是发动机,订单也不是支付处理器。这类强行继承不仅违反直觉,还会破坏封装,也容易踩中里氏替换原则的坑。
再看父类是不是“为继承而生”
即使语义对得上,也不能随便继承。需确认父类是否具备以下特征:
- 提供了 protected 钩子方法或模板方法(如
doProcess()、init()) - 文档或注释明确写着“可被子类化”或标有
@MustOverride - 关键方法未被
final修饰,且没有隐式调用可重写方法(例如构造器里调init()) - 本身不包含易变的业务逻辑(如
AbstractList可继承,ArrayList就不该被继承)
如果父类只是个普通工具类或具体实现类,没做任何继承友好设计,那它本质上就不欢迎被继承。
立即学习“Java免费学习笔记(深入)”;
组合怎么用才不是简单堆字段?
组合不是把对象 new 出来塞进字段就完事,关键是委托(delegation)+ 接口抽象:
- 把可变行为抽成接口(如
PaymentStrategy、Logger) - 宿主类持有该接口的引用,而非具体实现
- 通过构造函数注入依赖,避免 new 硬编码
- 方法内部调用接口方法,不关心背后是谁实现
这样,测试时可轻松注入 mock,上线时可切换不同策略,甚至运行时动态替换——这些能力继承完全做不到。
框架强制要求时怎么办?
有些场景绕不开继承:比如 HttpServlet、@Controller 类、JUnit 的 TestCase。这不是设计偏好,而是框架契约。此时建议:
- 在继承层做最小包装,只处理框架必需逻辑(如拦截、初始化)
- 把核心业务逻辑下沉到独立的组合类中
- 用适配器模式隔离框架耦合,让业务代码不依赖
javax.servlet这类 API
这样既满足框架要求,又保住业务代码的可测性与可维护性。


















