HashSet的唯一性靠hashCode()和equals()方法的正确重写实现:hashCode()用于快速分流,equals()用于最终确认;二者约定违反会导致去重失效。

Java 中用 HashSet 保证元素唯一,核心在于它底层基于 HashMap 实现,只存 key,且自动忽略重复——前提是对象正确重写 hashCode() 和 equals()。
HashSet 的唯一性靠什么?
HashSet 不允许重复元素,不是靠“检查每个已有元素”这种低效方式,而是依赖两个关键约定:
- 如果两个对象 equals() 返回 true,它们的 hashCode() 必须相同
- 如果两个对象 hashCode() 不同,HashSet 直接认为它们不相等,不会调用 equals()
也就是说:hashCode 是“快速分流”,equals 是“最终确认”。违反任一约定,就可能出现“明明相等却重复添加”或“本不相等却被当成同一个”。
基本用法:添加、查重、遍历
直接 new 一个 HashSet,add() 就自动去重:
立即学习“Java免费学习笔记(深入)”;
Set<String> set = new HashSet<>();
set.add("apple"); // ✅ 成功
set.add("apple"); // ❌ 无效果,size 不变
set.add("Apple"); // ✅ 成功(大小写敏感)
System.out.println(set.size()); // 输出 2
常见操作:
- contains(obj):判断是否存在(内部调用 hashCode + equals)
- remove(obj):删除指定元素(同样依赖哈希定位+equals匹配)
- 用 for-each 或迭代器遍历,顺序不保证(HashSet 无序)
自定义对象怎么保证唯一?
比如有个 User 类,想按 id 去重:
class User {
private int id;
private String name;
// 构造、getter 略
@Override
public int hashCode() {
return Objects.hash(id); // 只用 id 计算哈希
}
@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
}
}
这样,两个 id 相同的 User 实例,add 到 HashSet 时就会被当作重复项拒绝。
注意这些坑
- 不可变性很重要:如果对象加入 HashSet 后修改了影响 hashCode/equals 的字段(如上面 User 的 id),会导致后续 contains/remove 失效——因为哈希桶位置变了,但元素还留在原处
- null 允许且唯一:HashSet 可存一个 null,多次 add(null) 也只保留一个
- 不是线程安全:多线程并发读写需用 Collections.synchronizedSet() 或 ConcurrentHashMap.newKeySet()


















