复查 equals 与 hashCode 的关键是可验证逻辑+运行时行为双重校验,须逐项击穿契约违背、字段遗漏、可变性误用三类隐患,并通过单元测试和集合行为测试确保自反性、对称性、传递性、一致性、非空性及字段一致性和不可变性。

复查 equals 与 hashCode 的关键,不是“再看一遍代码”,而是用可验证的逻辑+运行时行为双重校验。隐患往往藏在契约违背、字段遗漏、可变性误用这三类地方,必须逐项击穿。
查契约完整性:五个原则缺一不可
equals 方法必须同时满足自反性、对称性、传递性、一致性、非空性。光靠肉眼容易漏掉边界情况:
- 写单元测试覆盖 null 入参(
obj == null分支是否返回 false) - 构造两个不同实例但字段值相同,验证
a.equals(b)和b.equals(a)是否都为 true(对称性) - 引入第三个对象 c,使 a.equals(b) && b.equals(c) 为 true,检查 a.equals(c) 是否也为 true(传递性)
- 同一对象多次调用 equals,结果是否稳定(一致性)
查字段一致性:equals 用哪些字段,hashCode 就必须用哪些
这是最常见隐患源。比如 equals 中比较了 id 和 name,但 hashCode 只用了 id,就会导致相等对象哈希码不同——HashMap 查不到、HashSet 重复存入。
- 把 equals 中参与比较的字段列出来,逐个对照 hashCode 实现
- 确认 hashCode 使用的是 完全相同的字段集合,且顺序一致(影响 Objects.hash() 结果)
- 特别注意:如果字段是数组,
Objects.hash(arr)只算引用,要用Arrays.hashCode(arr);多维数组必须用Arrays.deepHashCode()
查可变字段风险:别让 hash 值随对象变化
如果 hashCode 依赖了后续可能被修改的字段(如 setter 修改的 name),对象放进 HashMap 后再改字段,它就再也找不到了——因为桶位置已错。
立即学习“Java免费学习笔记(深入)”;
- 优先选用 final 字段或构造后不可变字段参与哈希计算
- 若业务强制需用可变字段,必须确保:该对象 绝不会作为 HashMap 的 key 或 HashSet 的元素
- 在类文档或注释中明确标注:“本类对象不可变,hashCode 基于 final 字段生成”
查工具链是否兜底:IDE/Lombok 生成 ≠ 一劳永逸
用 IDEA 自动生成或 Lombok 的 @EqualsAndHashCode 很方便,但隐患仍在:
- 检查 Lombok 注解是否加了
exclude或of参数,是否意外漏掉了关键字段 - IDE 生成时确认勾选了所有需参与比较的字段,且没误选了 transient 或计算字段
- 生成后手动补上 null 安全处理(如用
Objects.equals(a, b)而非a.equals(b))
真正消灭隐患,靠的是每次修改字段或语义后,重新跑一遍契约测试 + 集合行为测试。不复杂,但容易忽略。


















