Java中编写支持继承的equals方法,核心是用getClass()而非instanceof确保类型严格匹配以维护对称性;子类必须先调用super.equals()再比较自有字段;所有参与比较的逻辑应避免调用可重写方法,并配套重写hashCode。

Java 中编写支持继承的 equals 方法,核心是遵守 Object.equals 合同,同时避免因子类引入新字段或重写逻辑导致对称性、传递性被破坏。关键不是“强制要求子类调用父类 equals”,而是设计上让父子类能安全协作。
用 getClass() 而非 instanceof 判断类型
这是支持继承时最常被忽略却最关键的一点。若用 instanceof,父类 equals 可能错误地认为子类实例“等于”自己(违反对称性):
// ❌ 危险示例:父类用了 instanceof
public boolean equals(Object o) {
if (o == null || !(o instanceof Parent)) return false;
Parent p = (Parent) o;
return this.id == p.id;
}
// 子类 Child extends Parent { String tag; }
// 此时 new Parent(1).equals(new Child(1, "x")) → true
// 但 new Child(1, "x").equals(new Parent(1)) → false(Child 的 equals 通常会检查 tag)
// 违反对称性!
✅ 正确做法:在父类中用 getClass() == o.getClass(),确保“同类才可比”:
- 父类
equals只接受同类型实例,不向子类开放“降级比较” - 子类若想支持与父类实例相等,必须显式在自己的
equals中处理(通常不推荐,易出错) - 这样父子类各自实现互不干扰,对称性天然成立
子类 equals 必须先调用 super.equals
子类新增字段时,需在确认“父类部分相等”后再比较子类特有字段:
public class Child extends Parent {
private String tag;
@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false; // ✅ 先确保父类字段一致
if (!(o instanceof Child)) return false;
Child c = (Child) o;
return Objects.equals(tag, c.tag); // ✅ 再比较子类字段
}
}
- 调用
super.equals(o)是必须步骤,否则父类字段可能未校验 - 注意:此时
super.equals已用getClass()检查,所以o一定是Child类型(或更下层子类),但为安全仍建议二次instanceof或直接强转后判空
避免在 equals 中调用可被重写的方法
若父类 equals 内部调用了某个 protected 或非 final 方法(如 getDisplayName()),而子类重写了它,就可能导致比较逻辑不一致甚至无限递归:
- 所有参与比较的字段应直接访问,或通过
final方法获取 - 若必须抽象行为,把该方法声明为
final,或在equals中用私有辅助方法封装 - 尤其警惕 getter 中包含业务逻辑、状态变更或依赖外部对象的情况
配套重写 hashCode
只要重写了 equals,就必须重写 hashCode,且遵循相同原则:
- 用
getClass()判断是否同类(与equals保持一致) - 子类
hashCode应基于super.hashCode()和自身字段计算(例如Objects.hash(super.hashCode(), tag)) - 确保:如果
a.equals(b)为true,则a.hashCode() == b.hashCode()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











