必须重写 hashCode(),因为 Java 规范要求 equals() 为 true 的对象 hashCode() 必须相同,否则在 HashMap、HashSet 等哈希集合中会导致去重失效、get() 返回 null 等运行时异常;核心是参与 equals() 比较的字段也必须参与 hashCode() 计算,推荐使用 Objects.hash()。

修改 equals() 时必须重写 hashCode(),根本原因在于 Java 规范强制要求二者保持一致性:如果两个对象 equals() 返回 true,它们的 hashCode() 就必须返回相同的整数值。否则,对象在哈希集合中会“消失”或行为异常。
哈希集合依赖 hashCode 定位,再用 equals 确认
HashMap、HashSet 等容器不是逐个遍历比较,而是靠 hashCode() 快速算出对象该存到哪个“桶”(bucket)里。只有落在同一个桶里的对象,才会进一步调用 equals() 判断是否真正相等。
- 若只重写
equals(),但没改hashCode(),两个逻辑上相等的对象很可能因默认内存地址哈希值不同,被分到不同桶里 - 结果就是:
set.add(a)和set.add(b)(a.equals(b) 为 true)会同时成功——集合去重失效 - 或者
map.get(b)找不到已存入的a,因为哈希值不匹配,压根不会去查那个桶
违反契约的典型表现
常见问题不是编译报错,而是运行时逻辑错误,且难以排查:
- HashSet 中存了 10 个字段完全相同的对象,
size()却是 10 而不是 1 - HashMap 用自定义对象作 key,
put()成功,但后续get()返回null - Stream.distinct() 正常去重,但转成
new HashSet(list)后重复项仍在
怎么写才符合契约
重写 hashCode() 的核心是:参与 equals() 比较的字段,也必须参与 hashCode() 计算。
- 推荐用
Objects.hash(f1, f2, f3),它自动处理 null,并基于字段值生成稳定哈希 - 避免使用可变字段(如后期会修改的属性),否则对象放入 HashMap 后再改字段,哈希值变化会导致再也取不出来
- 如果
equals()只比较id,那hashCode()就只基于id;如果还加了name,哈希计算也得包含name
一个反例说明问题本质
假设 User 类只重写了 equals() 比较 id,但没动 hashCode():
-
new User("U001")和new User("U001")——equals()返回true - 但两者是不同实例,默认
hashCode()返回不同值(内存地址不同) - 放进
HashSet后,它们被散列到不同位置,集合认为是两个不同对象 - 这就直接破坏了“相等即同一”的语义,也违背了集合接口的设计契约










