必须同步重写equals和hashCode,因为HashMap等哈希容器先用hashCode定位桶、再用equals确认相等;若只重写equals,逻辑相等的对象可能散列到不同桶,导致get()返回null、HashSet重复添加等静默错误。

因为哈希容器(如HashMap、HashSet)依赖 先用 hashCode 定位桶,再用 equals 确认相等。如果只重写 equals 而不重写 hashCode,两个逻辑上相等的对象可能散列到不同桶中——后续 get()、contains() 或去重操作会彻底失效,且无任何编译错误或异常提示。
哈希容器的查找逻辑决定了二者必须同步
当你把对象放进 HashMap 或 HashSet 时,JVM 并不是遍历整个集合去比对 equals,而是:
- 先调用对象的 hashCode(),算出它该存进数组的哪个下标(即“桶”)
- 只在这个桶里,才逐个调用 equals() 检查是否真正相等
- 如果 hashCode 不一致,哪怕 equals 返回 true,也永远不会被比较到
不重写 hashCode 的典型故障表现
这些不是报错,而是静默逻辑错误,极难排查:
- new HashSet<>(Arrays.asList(p1, p2)) 含两个 equals 相等的对象,结果 size 为 2(本该是 1)
- map.put(p1, "A") 成功,但 map.get(p2) 返回 null,尽管 p1.equals(p2) == true
- Stream.distinct() 正常去重,但 new HashSet(list) 却重复 —— 因为前者只用 equals,后者依赖 hashCode + equals
合格的 hashCode 实现只有一个硬要求
参与 equals 判断的所有字段,必须全部参与 hashCode 计算。例如:
- 若 equals 只比 id,则 hashCode 仅基于 id
- 若 equals 比 id + name + email,则 hashCode 也必须包含这三者
- 推荐用 Objects.hash(id, name, email),它自动处理 null 和类型转换
- 避免使用可变字段(如 status、lastModified),否则对象入集合后修改这些字段,hashCode 改变 → 永远取不出来
继承场景下容易踩的坑
子类重写 equals 并引入新字段时:
- 不仅要重写自己的 hashCode,还要确保父类的 hashCode 已正确实现
- 否则可能出现:p1(父类实例).equals(p2(子类实例))为 true,但 hashCode 不同,违反契约
- 更安全的做法是:子类 hashCode 显式调用 super.hashCode(),再合并新增字段

















