HashSet通过HashMap键唯一性去重,要求元素正确重写hashCode()和equals();自定义类未重写时按内存地址判重;修改影响哈希值的字段会导致元素“丢失”;JDK包装类和String已内置正确实现。

Java 的 HashSet 通过底层 HashMap 的键唯一性机制自动避免重复添加,但前提是元素必须正确实现 hashCode() 和 equals() 方法。
底层靠 HashMap 的 key 唯一性
HashSet 内部不存数据,它只是把每个元素作为 key,存进一个 HashMap<E, Object> 中,value 固定为一个静态占位对象 PRESENT。所以:
- 调用
set.add(e)实际执行的是map.put(e, PRESENT) - HashMap 的
put()方法会先算e.hashCode()找桶位置 - 若桶中已有 key,再用
e.equals(已存在key)判断是否真相同 - 只有两者都满足(哈希值相等且
equals返回 true),才视为重复,put()返回旧 value,HashSet 就不添加
自定义类必须重写 hashCode 和 equals
如果往 HashSet 里放的是自定义对象(比如 User、Product),而没重写这两个方法,那默认用的是 Object 的实现——基于内存地址,两个内容相同的对象也会被当成不同元素。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 必须同时重写,不能只写一个
-
equals返回 true 的两个对象,hashCode必须返回相同整数 - 参与
equals比较的字段,也要参与hashCode计算 - 建议用 IDE 自动生成(如 IntelliJ 的
Alt+Insert→ “Override Methods”)
字段修改后可能“丢失”元素
一旦对象加入 HashSet,就不要再修改影响 hashCode() 或 equals() 结果的字段。否则:
立即学习“Java免费学习笔记(深入)”;
- 对象原哈希桶位置找不到它(
contains()返回 false) - 它仍留在旧桶里,变成“幽灵元素”,无法删除也无法查到
- 更糟的是,修改后再次
add()可能被当成新元素,导致逻辑重复 - 安全做法:先
remove(),改完再add()
基本类型和包装类不用操心
String、Integer、Long 等 JDK 自带类已经正确实现了 hashCode() 和 equals(),直接放进 HashSet 就能去重:
-
set.add(5); set.add(5);→ 第二个无效 -
set.add("abc"); set.add(new String("abc"));→ 只存一份

















