“has-a”表示组合或聚合关系,通过成员变量实现,体现“拥有”语义;“is-a”表示继承或实现关系,通过extends/implements实现,体现“属于”语义并支持多态。

Java中“has-a”和“is-a”是两种根本不同的对象关系,区分关键在于语义依赖和代码实现方式:前者表达组合或聚合(成员变量引用),后者体现继承或实现(extends / implements)。
has-a 关系:通过成员变量建立的“拥有”关系
表示一个类“拥有”另一个类的实例,属于组合(composition)或聚合(aggregation)。这种关系不改变类的类型层级,仅体现结构上的依赖。
- 实现方式:在类内部声明另一个类类型的字段(如 private Engine engine;)
- 生命周期通常相关:组合中被拥有对象随拥有者一同创建/销毁;聚合中可独立存在(如部门有员工,但员工可调岗)
- 不能用子类替换:Car 类 has-a Engine,但不能把 Car 当作 Engine 使用
- 例子:class Car { private Engine engine; private List
wheels; } —— Car 拥有 Engine 和 Wheel,不是 Engine 或 Wheel 的子类型
is-a 关系:通过继承或实现建立的“属于”关系
表示一个类“是一种”另一个类,反映类型兼容性与多态基础,是面向对象的核心抽象机制。
- 实现方式:用 extends(类继承父类)、implements(类实现接口)
- 满足里氏替换原则:子类对象可直接赋值给父类或接口引用(Animal a = new Dog();)
- 强调行为契约:Dog is-a Animal,意味着 Dog 必须能完成 Animal 所承诺的行为(如 makeSound())
- 注意:Object 是所有类的隐式父类,所以任何类都 is-a Object
容易混淆的边界情况
有些设计看似像 is-a,实为 has-a,需结合语义判断:
立即学习“Java免费学习笔记(深入)”;
- StringBuffer 有 append() 方法,但它不是 List 或 Collection —— 它只是内部用了数组,属于 has-a 数组,不是 is-a 集合
- “Employee has-a Address” 是典型 has-a;但若建模为 class Address extends Location,则 Address is-a Location —— 继承层级与业务含义必须一致
- 接口实现是 is-a:类 implements Serializable,说明该类“是一种可序列化的类型”,支持序列化协议
建模建议:先问业务,再选语法
不要为了复用而强行继承,也不要为解耦而一律组合。真实建模应从问题域出发:
- 问:“B 在业务上是否属于 A 的一种?” → 是 → 考虑 is-a(继承/实现)
- 问:“A 是否需要持有 B 的实例来完成自身职责?” → 是 → 用 has-a(字段+构造/注入)
- 优先使用组合:更灵活、易测试、避免继承带来的紧耦合和脆弱基类问题
- 接口比抽象类更适合表达 is-a 的契约,尤其当多个不相关类需共享能力时(如 Flyable、Payable)










