hashset能自动去重,因其底层基于hashmap,元素作为key存储,依赖equals()和hashcode()保障唯一性;自定义类必须正确重写这两个方法,否则去重失效。

HashSet 为什么能自动去重
因为 HashSet 内部基于 HashMap 实现,添加元素时实际是把元素作为 HashMap 的 key 存入,而 HashMap 的 key 天然不允许重复——这层逻辑由 equals() 和 hashCode() 共同保障。
所以你不需要手动判断是否已存在,调用 add() 就行;但前提是:自定义类必须正确重写 equals() 和 hashCode(),否则即使内容相同也会被当作不同对象存入。
基本用法和常见错误场景
直接 new 一个 HashSet,用 add() 插入,用 contains() 查询,用 size() 获取数量——这些都没问题。容易出错的是以下情况:
- 往
HashSet<string></string>里加null:合法,HashSet允许一个null元素 - 往
HashSet<list>></list>里加两个内容相同的ArrayList:如果没重写hashCode(),它们可能被当成不同元素(因为默认是对象地址哈希) - 在多线程环境下直接使用
HashSet:它不是线程安全的,会丢数据;该用Collections.synchronizedSet(new HashSet())或ConcurrentHashMap.newKeySet()
和 LinkedHashSet、TreeSet 的关键区别
三者都实现 Set 接口,但行为差异直接影响选型:
-
HashSet:无序、非线程安全、O(1) 平均插入/查找,依赖hashCode() -
LinkedHashSet:按插入顺序迭代,额外维护链表,内存略高,但遍历时可预测顺序 -
TreeSet:自动排序(需实现Comparable或传Comparator),底层是红黑树,O(log n) 操作,不依赖hashCode()
如果你只关心“有没有”,不关心“什么时候加的”或“谁大谁小”,就用 HashSet;别为了省几行代码强行用 TreeSet 去重,排序开销白搭。
自定义类必须重写的两个方法
比如有个 User 类,想按 id 去重:
public class User {
private final int id;
private final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id; // 只看 id 判等
}
@Override
public int hashCode() {
return Objects.hash(id); // 只对 id 计算哈希
}
}
漏掉其中任一方法,或者 hashCode() 里用了 name 而 equals() 没用,都会导致去重失效——这是最常被跳过的验证点。










