hashset通过hashmap实现,依赖hashcode()定位桶、equals()判断重复;自定义类须重写二者以保证逻辑相等对象不重复,否则因默认实现基于内存地址导致去重失效。

HashSet 通过哈希表(底层是 HashMap)实现,利用对象的 hashCode() 和 equals() 方法共同保证元素不重复。
核心机制:先算哈希,再比内容
当你调用 add(e) 时,HashSet 实际委托给内部的 HashMap,把元素 e 作为 key、一个固定对象(如 PRESENT)作为 value 存入。关键步骤如下:
- 计算元素 e 的
hashCode(),确定它在哈希表中的桶(bucket)位置 - 如果该桶为空,直接存入
- 如果桶中已有元素,逐个调用
equals()比较——只有hashCode()相同 且equals()返回 true,才判定为重复,添加失败
为什么必须重写 hashCode 和 equals?
如果你自定义类(比如 Person)放进 HashSet,但没重写这两个方法,会沿用 Object 默认实现:
-
Object.hashCode()返回对象内存地址的整数表示,不同实例几乎一定不同 -
Object.equals()判断是否为同一对象(==),两个内容相同但不同实例的 Person 会被视为不同元素 - 结果:逻辑上相同的对象可能被重复添加,违反“不重复”预期
✅ 正确做法:只要两个对象 equals() 返回 true,它们的 hashCode() 就必须相同;反之不强制要求,但尽量均匀分布以提升性能。
常见陷阱与注意事项
以下情况容易导致去重失效或行为异常:
-
修改已存入 HashSet 的可变对象的字段:若修改影响了
hashCode()或equals()的计算结果,该对象可能再也无法被找到(因为查找时去了错误的桶),甚至造成“假重复” -
使用 null 元素:HashSet 允许一个 null,它的
hashCode()是 0,equals(null)有特殊处理,安全可用 -
线程不安全:HashSet 不是线程安全的,多线程并发 add 可能引发数据错乱,需外部同步或改用
Collections.synchronizedSet()/ConcurrentHashMap.newKeySet()
简单验证示例
定义一个正确重写了 hashCode 和 equals 的类:
String name;
int age;
// 构造、getter 省略
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
public int hashCode() { return Objects.hash(name, age); }
}
然后:
Setset.add(new Person("Alice", 25));
set.add(new Person("Alice", 25)); // 不会添加成功,size 仍为 1
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











