HashSet去重需hashCode()与equals()协同:hashCode()定位桶,equals()确认相等;自定义类必须同时重写二者以保证契约一致,否则导致去重失败或误判。

HashSet 的去重不是单靠 hashCode() 完成的,而是它和 equals() 协同工作的结果——hashCode() 负责“快速定位”,equals() 负责“最终确认”。
当你调用 set.add(obj) 时,底层实际执行的是 map.put(obj, PRESENT)。而 put 的判断逻辑,就围绕 hashCode() 和 equals() 展开。
hashCode() 的作用是桶定位
HashSet 底层是一个哈希表(数组 + 链表/红黑树)。obj.hashCode() 经过扰动运算(如 h ^ (h >>> 16))后,再与数组长度取模(或用 & (n-1)),得到该对象应该存入的桶索引(bucket index)。
这意味着:
- 相同对象(逻辑相等)必须返回相同
hashCode(),否则可能被散列到不同桶里,equals()根本没机会被调用 → 去重失败 - 不同对象尽量返回不同
hashCode(),减少哈希冲突,提升性能
但 hashCode() 不能单独决定是否重复
因为哈希碰撞不可避免。例如 "Aa".hashCode() == "BB".hashCode() 返回 true,但它们明显不是同一个字符串。
所以仅靠 hashCode() 判断重复,会误判——把不相等的对象当成重复,导致漏存。
真正的去重发生在 equals() 比较阶段
当 hashCode() 算出的桶位置已有元素时,HashSet 会遍历该桶内的所有节点,对每个已存在元素调用 obj.equals(existing):
- 如果某次
equals()返回true,说明是重复元素,add()返回false,不插入 - 如果遍历完都没匹配上,才把新元素加入该桶
这就像机场安检:
-
hashCode()是 X 光机扫描(快、粗筛,把可疑行李分到对应通道) -
equals()是人工开箱检查(慢、精判,确认是不是真违禁品)
自定义类必须同时重写两个方法
若只重写 equals() 而不重写 hashCode():
- 两个逻辑相同的对象可能算出不同哈希值 → 分到不同桶 →
equals()不会被调用 → 被当作不同元素插入
若只重写 hashCode() 而不重写 equals():
立即学习“Java免费学习笔记(深入)”;
- 默认
equals()比较的是内存地址 → 即使字段完全一样,不同实例也返回false→ 无法去重
正确做法是保持契约一致:
- 若
a.equals(b)为true,则a.hashCode()必须等于b.hashCode() - 通常用相同字段参与
hashCode()计算和equals()比较(比如Objects.hash(name, age)+ 字段逐一对比)
不复杂但容易忽略


















