hashset能去重,因其底层基于hashmap,插入时先通过hashcode定位桶位置,再用equals比较元素内容,只有两者均相同才视为重复;自定义类须同时重写hashcode和equals。

HashSet 通过哈希码(hashCode)和 equals 方法协同工作,自动保证元素唯一性,底层用 HashMap 存储(key 为元素,value 为固定对象),插入时先算 hash 定位桶位置,再用 equals 判重。
为什么 HashSet 能去重?关键在 hashCode + equals
HashSet 不允许重复元素,判断逻辑分两步:
- 先调用元素的 hashCode(),快速定位到哈希表中的“桶”(数组索引)
- 若该桶已有元素,则遍历桶内链表或红黑树,逐个调用 equals() 比较内容是否相等
- 只有 hashCode 相同 且 equals 返回 true,才视为重复,跳过添加
⚠️ 注意:如果自定义类作为元素,必须同时重写 hashCode 和 equals,否则可能造成逻辑错误(比如两个逻辑相等的对象因 hashCode 不同被存入两次)。
基本用法示例:创建、添加、验证唯一性
使用非常简单,但要注意初始化和类型约束:
- 声明:`Set
set = new HashSet();`(推荐接口引用) - 添加:`set.add("a"); set.add("b"); set.add("a");` → 第二个 "a" 添加失败,返回 false
- 验证:`System.out.println(set.size());` 输出 2;`System.out.println(set);` 输出 [a, b](顺序不保证)
HashSet 不保证迭代顺序,如需有序,请用 LinkedHashSet;如需排序,用 TreeSet。
自定义类使用 HashSet 的正确姿势
以 Person 类为例,必须重写两个方法:
<font size="2">public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}</font>
之后就能安全使用:
- `Set
people = new HashSet();` - `people.add(new Person("Alice", 25));`
- `people.add(new Person("Alice", 25));` → 不会重复添加
性能与注意事项
- 平均时间复杂度 O(1) 插入/查找,但极端哈希冲突下退化为 O(n)
- 允许一个 null 元素(因为 HashMap 的 key 可为 null)
- 线程不安全,多线程场景请用 Collections.synchronizedSet 或 CopyOnWriteArraySet
- 初始容量和加载因子可调优:`new HashSet(16, 0.75f)`,减少扩容开销
不复杂但容易忽略细节,核心就两条:用对数据结构,写对 equals/hashCode。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











