正确重写equals需满足契约并兼容继承:用getclass()而非instanceof确保类型严格匹配,子类先校验再调用super.equals()复用父类逻辑,同步重写hashcode且包含所有参与比较的字段,避免副作用。

在继承结构中正确重写 equals 方法,核心是:**既要满足对称性、传递性、一致性等契约,又要兼容父类与子类之间的比较逻辑**。常见错误是忽略类型检查的严格性或破坏 Liskov 替换原则,导致 equals 行为不可预测。
用 getClass() 而非 instanceof 判断类型
若父类的 equals 使用 instanceof,子类重写时可能违反对称性。例如:parent.equals(child) 返回 true,但 child.equals(parent) 因子类加了额外字段校验而返回 false。
推荐做法:在每个类的 equals 中使用 obj.getClass() == this.getClass(),确保只有同一具体类型才可能相等。这牺牲了“父类实例与子类实例相等”的灵活性,但换来语义清晰和契约安全。
- 适用于大多数业务实体类(如
User、Order),不希望子类“向上兼容”比较 - 若确实需要跨层级比较(如抽象类
Shape的子类Circle和Rectangle都可与Shape实例比较),则需在抽象父类中定义明确的比较策略,并由所有子类严格遵循
调用父类 equals 前先校验类型和非空
子类重写时,应先完成基础校验(非空、同类),再调用 super.equals(obj),让父类字段比较逻辑复用,避免重复代码和遗漏。
示例结构:
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false; // 类型严格匹配
SubClass that = (SubClass) obj;
if (!super.equals(obj)) return false; // 复用父类字段比较
return Objects.equals(extraField, that.extraField);
}
- 注意:传给
super.equals()的是原始obj,不是强制转型后的that;否则父类内部getClass()检查会失败 - 如果父类未正确实现
equals(比如用了instanceof),子类无法靠调用super.equals()修复问题,必须整体重写或推动父类修正
配套重写 hashCode 并保持一致性
equals 和 hashCode 必须同步维护:相等的对象必须有相同哈希码;参与 equals 比较的字段,也必须全部参与 hashCode 计算。
- 使用
Objects.hash(field1, field2, ...)生成哈希码,简洁且自动处理null - 若子类新增字段用于
equals判断,则hashCode中必须包含它;否则放入HashSet或作HashMap键时行为异常 - 父类已重写的
hashCode若未覆盖全部参与比较的字段,子类不能直接调用super.hashCode()后叠加——需重新计算完整哈希值
避免在 equals 中引入副作用或耗时操作
标准契约要求 equals 是纯函数:无状态修改、无 I/O、快速返回。继承结构中尤其要注意:
- 不要在
equals中触发懒加载(如 Hibernate 代理初始化)、远程调用或复杂计算 - 若字段本身是延迟计算或依赖外部状态(如缓存、时间戳),应考虑是否真该参与相等性判断;否则容易导致不一致
- 对于集合类字段(如
List、Set),用Objects.equals(a, b)安全比较,它自动处理null和不同类型集合的语义(如ArrayList与LinkedList内容相同即相等)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











