HashSet通过HashMap实现去重,依赖hashCode()和equals()协同判断重复;自定义类须重写这两个方法,否则默认引用比较导致去重失效。

Java 中 HashSet 通过内部基于 HashMap 实现,利用对象的 hashCode() 和 equals() 方法协同判断元素是否重复,从而天然保证元素唯一性与自动去重。
HashSet 去重的核心机制
HashSet 底层是一个 HashMap,它把添加的元素作为 key 存入,value 固定为一个静态的 PRESSENT 对象。所以去重逻辑完全复用 HashMap 的 key 冲突处理规则:
- 当调用
add(e)时,先计算e.hashCode(),定位到哈希桶位置 - 若桶中无元素,直接插入;若有元素,则逐个调用
e.equals(已存在元素) - 只要
hashCode()相同且equals()返回true,就认为是重复元素,添加失败(返回false)
自定义类必须重写 hashCode 和 equals
对于 String、Integer 等 JDK 内置类型,JDK 已重写好这两个方法,可直接用于 HashSet。但自定义类(如 User)若不重写,会继承 Object 的默认实现 —— hashCode() 返回内存地址,equals() 比较引用,导致逻辑上相同的对象仍被当作不同元素。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
正确做法示例:
立即学习“Java免费学习笔记(深入)”;
<font size="2">public class User {
private String name;
private int age;
// 构造、getter 省略...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}</font>
这样两个 name 和 age 完全相同的 User 实例,就能被 HashSet 正确识别为重复并去重。
常见去重使用场景与技巧
- 简单去重:直接 add 所有元素,无需额外判断 —— HashSet 自动过滤重复项
-
保留首次出现顺序:用
LinkedHashSet替代 HashSet,它在去重基础上维持插入顺序 -
忽略大小写字符串去重:存入前统一转小写(如
set.add(str.toLowerCase())),或使用自定义包装类 + 重写 equals/hashCode - 避免空指针:添加前检查 null(HashSet 允许一个 null 元素,但多个 null 会被视为重复)
注意点与易错提醒
- 修改已加入 HashSet 的对象字段(尤其是影响
hashCode()或equals()的字段),可能导致集合“丢失”该元素或无法remove()—— 因为查找时 hash 桶位置已变 - HashSet 不是线程安全的,多线程环境下需用
Collections.synchronizedSet(new HashSet())或ConcurrentHashMap.newKeySet() - 不要依赖 HashSet 的遍历顺序 —— 它是无序的(JDK 8 后底层用红黑树优化长链表,但顺序仍不保证)

















