必须同时重写 hashCode 和 equals,否则 HashSet/HashMap 无法识别逻辑相等的对象;参与 hashCode 计算的字段须与 equals 完全一致、不可变,且同一对象哈希值在运行期恒定。

不重写 hashCode,HashSet 和 HashMap 就会“认不出”相等的对象
当你只重写了 equals 却没动 hashCode,Java 会继续用 Object.hashCode() —— 那个基于内存地址的默认实现。结果就是:两个逻辑上完全相等的对象(a.equals(b) == true),却返回不同的哈希值(a.hashCode() != b.hashCode())。
而 HashSet 查找元素时,第一步永远是算哈希值、定位桶(bucket);如果哈希值不同,它根本不会去调用 equals 比较——直接判定“不存在”。
所以你会看到这种诡异现象:
-
set.add(s1)成功,set.contains(s2)却返回false,哪怕s1.equals(s2)是true - 同一个键在
HashMap里被重复插入,导致看似“覆盖失败” - 对象进了集合却再也取不出来,debug 时怀疑人生
怎么安全地重写 hashCode?记住三个硬约束
重写不是随便加个数字,必须和 equals 严格对齐:
- 参与
hashCode计算的字段,必须和equals中用于判断相等的字段 完全一致(多一个、少一个、类型不匹配都会破防) - 字段不能是可变的(比如后续会
setName()修改的name),否则对象放进HashSet后改了字段,哈希值变了,就再也找不回来了 - 同一对象在单次 JVM 运行中,只要参与计算的字段没变,
hashCode()必须返回相同值(哪怕你重启应用,也不要求跨次一致)
推荐直接用 Objects.hash(name, age) —— 它自动处理 null、顺序敏感、性能够用,比手写 31 * name.hashCode() + age 更少出错。
为什么不能只靠 equals?性能差到没法忍
假设你有个含 10 万条记录的 ArrayList,每次 contains 都得遍历比较字段,平均要查 5 万次;换成 HashSet,理想情况下一次哈希定位 + 最多几次 equals 就搞定。
但这个加速前提是:哈希分布合理、冲突少、且相等对象能落到同一个桶里。一旦 hashCode 没重写,所有对象都散列到不同位置,HashSet 就退化成“带哈希表壳的链表”,不仅没提速,还多了一层哈希计算开销。
立即学习“Java免费学习笔记(深入)”;
常见错误:重写了 hashCode 但字段选错了
比如 Person 类中,equals 只比 id,但 hashCode 却用了 name 和 age:
public boolean equals(Object o) {
if (o instanceof Person) {
return this.id == ((Person) o).id; // 仅 id 决定相等
}
return false;
}
public int hashCode() {
return Objects.hash(name, age); // ❌ 错!这里该用 id
}这会导致:两个 id 相同但 name 不同的 Person 对象,equals 返回 true,hashCode 却不同 —— 违反契约,集合行为不可预测。真正该写的是:return Objects.hash(id);
最容易被忽略的一点:重写 hashCode 不是为了“让哈希值看起来更随机”,而是为了守住 equals 和哈希容器之间的信任链——断了,集合就失效;松了,性能就垮了。











