HashSet唯一性依赖equals()与hashCode()协同校验:若equals()为true则hashCode()必须相同,否则可能跳过equals比较导致重复;自定义类须同时重写二者,且加入后勿修改参与哈希计算的字段。

HashSet 的唯一性靠的是 equals() + hashCode() 两层校验
不是只看哈希值,也不是只比内容。Java 要求:如果两个对象 equals() 返回 true,它们的 hashCode() 必须相同;反过来不强制,但若不同,HashSet 会直接认为它们不在同一个桶里,根本不会调用 equals() 去比较。
所以唯一性失效的常见原因只有一个:重写了 equals() 却没重写 hashCode()。
- 自定义类加入
HashSet前,必须同时重写equals(Object)和hashCode() - IDE(如 IntelliJ)生成的
hashCode()默认基于所有参与equals判断的字段,别手动删掉某字段的哈希计算 - 字段值在对象加入
HashSet后被修改,且该字段参与了hashCode()计算 → 后续contains()或remove()可能失败
HashSet 底层是 HashMap,元素存在 key 位置,value 固定为 Presentation 静态对象
翻 JDK 源码能看到:HashSet 的 add(E) 实际调用的是内部 HashMap 的 put(e, PRESENT)。这意味着:
-
HashSet的性能、扩容逻辑、线程不安全性,完全继承自HashMap - 初始容量默认是 16,负载因子 0.75 → 实际能存约 12 个元素才触发扩容
- 哈希冲突时,JDK 8+ 会将链表转为红黑树(当桶中节点 ≥ 8 且 table.length ≥ 64),前提是 key 类型实现了
Comparable
常见误判场景:浮点数、时间、数据库实体做 HashSet 元素时容易重复
不是哈希机制出错,而是对象语义和 equals() 实现不匹配:
立即学习“Java免费学习笔记(深入)”;
-
Double.NaN的equals()返回true,但NaN == NaN是false;而Double.hashCode()对所有NaN返回同一固定值(0x7ff8000000000000L),所以多个NaN在HashSet中仍视为一个 -
java.util.Date的equals()比毫秒值,但若用new Date()创建两个“看起来一样”的时间(比如都格式化为 "2024-01-01"),实际毫秒数可能差几毫秒 →equals()为false,就会被当成不同元素 - JPA 实体若未重写
equals()/hashCode(),默认用内存地址比较,即使主键相同也会被当作不同对象加入HashSet
验证是否真唯一:别只看 size(),要查 contains() 行为
有时候你以为加进去了两个相同对象,其实是 add() 返回 false,但你没检查返回值:
HashSetset = new HashSet<>(); boolean r1 = set.add("hello"); boolean r2 = set.add("hello"); // r2 == false System.out.println(set.size()); // 输出 1 System.out.println(r1 + ", " + r2); // true, false
更隐蔽的问题是:自定义类的 hashCode() 返回常量(比如永远返回 1),会导致所有元素挤进同一个桶,退化成链表遍历,add() 仍能保证唯一,但性能暴跌 —— 这时候 size() 是对的,但响应时间暴露问题。
哈希机制本身很稳,真正出问题的地方,永远在你怎么定义“相同”。










