组合优于继承是指在多数代码复用场景中,用成员变量持有对象(has-a)比类继承(is-a)更稳妥可控;核心在于区分类型归属与功能复用,避免继承滥用导致的扩展性差、维护难等问题。

“组合优于继承”不是说继承不能用,而是指在大多数需要复用代码的场景下,用“把一个对象当成员变量持有”比“让一个类去 extends 另一个类”更稳妥、更可控。
核心区别:is-a 还是 has-a?
继承表达的是is-a(是一个)关系:比如 Dog extends Animal,狗是一种动物,逻辑自然成立。而组合表达的是has-a(有一个)关系:比如 Car 有一个 Engine,一个 Transmission,这种结构更贴近现实,也更容易调整。
一旦把“功能复用”误当成“类型归属”,就容易掉进继承陷阱。例如让 Ostrich(鸵鸟)继承带 fly() 方法的 Bird 类——鸵鸟不是不会飞的鸟,它是根本不能飞的鸟。硬继承只能靠抛异常或空实现来“打补丁”,这违背了设计本意。
为什么组合更可靠?
- 父类改了,子类不一定崩:组合依赖接口或抽象行为,只要接口不变,内部实现怎么换都行;继承则可能因父类方法签名、调用顺序、protected 成员变动而连带出错。
-
行为可以动态切换:比如汽车运行时从燃油引擎换成电动引擎,只需替换
engine字段引用;继承关系在编译期就锁死了,没法 runtime 换“爹”。 -
避免单继承限制:Java 不允许多继承,但一个类可以同时持有
FlyAbility、TweetAbility、SwimAbility多个对象,灵活拼装能力。 - 测试和维护更简单:每个组件可独立单元测试;修改导航模块,不用动整车逻辑;而继承链一改,往往要通读三四层才能确认影响范围。
怎么写出“组合友好”的代码?
- 把可变行为抽成接口,比如
Flyable、Drawable、Serializable; - 在主类中声明接口类型的字段,如
private Flyable flyer;; - 通过构造器或 setter 注入具体实现,而不是 new 出固定类型;
- 避免为复用几行代码就拉一条继承线——问问自己:这个子类真的“是”父类的一种吗?还是只是“用”了它的某部分能力?
基本上就这些。组合不是拒绝继承,而是把继承留给真正符合 is-a 的场合,比如 ArrayList 是一种 List,IOException 是一种 Exception;其余时候,优先考虑“拿过来用”,而不是“认它做爹”。
立即学习“Java免费学习笔记(深入)”;










