hashset去重依赖hashcode定位桶和equals精确比对协同工作;若只重写equals不重写hashcode,逻辑相等对象可能散列到不同桶,导致查找不到或重复插入。

因为 HashSet 底层用 HashMap 实现,而 HashMap 依赖 先算 hashCode 定位桶、再用 equals 精确比对 的两步机制。如果只重写 equals 不重写 hashCode,逻辑相等的对象可能被散列到不同桶中,导致去重失效、查找不到等问题。
HashSet 的去重逻辑依赖两个方法协同工作
HashSet 并不是把所有元素拉出来挨个 equals 比较。它实际是:
- 调用对象的
hashCode(),算出一个整数,再映射到内部数组(哈希表)的某个索引位置(“桶”) - 若该桶为空,直接存入;若已有元素,再逐个调用
equals()判断是否真正相等 - 只有
hashCode相同 且equals返回true,才视为重复,拒绝插入
不重写 hashCode 的典型后果
假设 Person 类只重写了 equals(按 name 和 age 判断相等),但没动 hashCode:
-
p1.equals(p2)返回true(内容相同) -
p1.hashCode() != p2.hashCode()(仍用 Object 默认实现,基于内存地址) - 结果:
set.add(p1); set.contains(p2)返回false—— 明明相等却查不到 - 更严重的是:
set.add(p2)会成功插入,造成集合中出现“逻辑重复”的对象
重写时必须遵守的关键约束
二者不是随便写写,要满足 Java 规范的硬性契约:
- 如果
a.equals(b) == true,那么a.hashCode() == b.hashCode()必须成立 - 计算依据必须一致:equals 中参与比较的字段(如 id、name、age),hashCode 也必须全部纳入运算
- 注意 null 安全:字段可能为 null,推荐用
Objects.hashCode(field),避免空指针 - IDE 自动生成(如 IntelliJ 的 Alt+Insert → “equals and hashCode”)比手写更可靠,不易漏字段或写错逻辑
为什么 String、Integer 等类不用我们操心?
因为它们已经由 JDK 正确实现了:
-
String.equals()比较字符序列内容,String.hashCode()也是基于相同字符序列计算 - 这种一致性保证了它们作为 key 存入 HashSet 或 HashMap 时行为稳定
- 你的自定义类没有这种保障,必须自己补上
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











