重写equals方法必须严格遵循Java契约,包括自反性、对称性、传递性、一致性和对null的处理,否则会导致集合类行为异常;标准模板含自引用检查、类型校验、强转及Objects.equals安全比对;且必须同步重写hashCode,确保参与字段完全一致。

重写 equals 方法不是简单地加几行逻辑,而是要严格遵循 Java 的契约(contract),否则可能引发集合类(如 HashSet、HashMap)行为异常、判等结果不一致等问题。核心是:先满足规范,再嵌入业务逻辑。
必须遵守的 equals 契约
Java 规定 equals 必须满足五个条件:自反性、对称性、传递性、一致性、对 null 的处理。违反任一条件,都可能导致不可预期的 bug。例如:
- 若
a.equals(b)返回true,但b.equals(a)返回false(破坏对称性),HashMap可能找不到已存入的键; - 若未判空就直接调用字段方法,遇到
null会抛NullPointerException; - 若只比业务字段,却忽略对象是否为同一实例(即
this == obj),性能差且语义不严谨。
标准重写模板(含业务字段注入)
以一个订单类 Order 为例,业务上认为「订单号相同即为同一订单」,但需兼顾金额、用户 ID 是否参与比对(依实际场景而定):
@Override
public boolean equals(Object obj) {
// 1. 自引用检查(提升性能 + 处理 null)
if (this == obj) return true;
// 2. 类型检查(避免 ClassCastException)
if (obj == null || getClass() != obj.getClass()) return false;
// 3. 强转(此时类型安全)
Order order = (Order) obj;
// 4. 业务字段逐个比较(用 Objects.equals 避免空指针)
return Objects.equals(orderNo, order.orderNo) &&
Objects.equals(userId, order.userId) &&
Double.compare(order.amount, amount) == 0;
}
说明:
立即学习“Java免费学习笔记(深入)”;
-
Objects.equals(a, b)内部自动处理null,比a != null && a.equals(b)更简洁安全; - 数值类型如
double用Double.compare,避免==对NaN或精度问题误判; - 字符串、日期、自定义对象字段,只要它们自身正确重写了
equals,就可放心用Objects.equals; - 如果业务只要求「订单号唯一判定」,那就只比
orderNo—— 但务必确认这是真实需求,而非偷懒。
千万别漏掉 hashCode 方法
equals 和 hashCode 必须同步重写。规则是:如果两个对象 equals 返回 true,它们的 hashCode 必须相等。否则放入 HashSet 或作为 HashMap 键时会失效。
推荐用 IDE 自动生成(如 IntelliJ 的 Alt+Insert → equals and hashCode),或手写时用 Objects.hash:
@Override
public int hashCode() {
return Objects.hash(orderNo, userId, amount);
}
注意:hashCode 中参与计算的字段,必须和 equals 中用于判断相等的字段完全一致。
常见陷阱与提醒
- 不要在
equals中修改对象状态(比如 lazy-init 字段),这违反契约且导致不可预测行为; - 避免使用
instanceof替代getClass() != obj.getClass(),否则子类实例可能错误地等于父类实例(破坏对称性); - 数据库主键(如 id)是否参与比对?若对象尚未持久化(id 为 null),仅靠 id 判等会失效,此时应结合业务唯一标识(如订单号、手机号等);
- Lombok 的
@EqualsAndHashCode很方便,但要显式指定include或exclude字段,防止把非业务字段(如临时缓存、日志标记)混入比对逻辑。


















