排查HashSet/HashMap去重失效,核心是验证equals与hashCode是否成对重写且契约一致:二者必须同时存在、字段相同、计算稳定;若对象加入集合后修改影响hashCode的字段,会导致contains/remove失效。

排查 HashSet 或 HashMap 去重/查找失效的 Bug,核心是验证对象是否真正满足 equals 与 hashCode 的契约。问题通常不出在集合本身,而在于你传进去的对象没“说清楚自己是谁”。
检查 equals 和 hashCode 是否成对重写
这是最常见、最高发的问题。如果只重写了 equals,没重写 hashCode,HashSet 就根本不会调用 equals——它先按 hashCode 找桶,桶都不在一个地方,自然无法比内容。
- 打开你的实体类,确认两个方法都存在且被 @Override 标记
- 如果用了 Lombok,检查是否加了
@EqualsAndHashCode,并确认include或of指定的字段和业务逻辑一致(比如漏掉了 id,却只用了 name) - 特别注意:继承自父类的字段,若参与相等判断,必须显式包含在重写逻辑中
验证参与计算的字段是否稳定且合理
即使两个方法都写了,若字段在对象加入集合后被修改,会导致后续 contains/remove 失效——因为 hashCode 变了,原对象“找不回自己待的桶”。
- 运行时打印关键字段值:添加前、修改后、调用 contains 之前,三者是否一致?
- 检查 hashCode 计算所依赖的字段(如 name、id)是否为 final,或至少在加入集合后不再变更
- 避免用可变集合(如 ArrayList)、数组、Date 等作为 hashCode 输入;数组要用
Arrays.hashCode(),而非直接传入 Objects.hash
手动模拟集合内部判断流程
把 HashSet 的 add 过程拆解出来,用调试或日志还原每一步:
立即学习“Java免费学习笔记(深入)”;
- 调用
obj.hashCode(),记下返回值 - 创建另一个内容相同的对象,也调用它的
hashCode(),看是否相等 - 若 hashCode 相同,再调用
obj1.equals(obj2),确认是否 true - 如果 hashCode 不同 → 问题出在 hashCode 实现;如果 hashCode 相同但 equals 返回 false → equals 实现有误(比如字段名拼错、null 判断遗漏、类型强转异常)
用单元测试覆盖典型场景
写几个小而准的测试,能快速暴露契约破坏:
- 相同字段构造两个对象,验证
o1.equals(o2) == true且o1.hashCode() == o2.hashCode() - 不同字段的对象,验证
o1.equals(o2) == false(不必要求 hashCode 不同,但最好尽量不同) - 把对象加入 HashSet 后修改影响 hashCode 的字段,再调用
set.contains(该对象),应返回 false —— 这不是 bug,而是提醒你设计有问题


















