未重写equals和hashcode会导致hashset内存泄漏,因其默认基于引用比较和地址哈希,使内容相同对象被重复添加且无法删除;重写须遵守相等对象哈希值相同、哈希值计算字段不可变等契约。

合理重写 equals 和 hashCode 是避免 HashSet 内存泄漏的关键,核心在于保证“逻辑相等的对象必须具有相同哈希码”,且“对象存入集合后哈希码不可变”。
为什么没重写会导致内存泄漏
HashSet 底层用 HashMap 存储,元素作为 key。添加时先根据 hashCode() 定位桶位置,再用 equals() 判重;删除时同样依赖这两个方法定位并匹配。
若未重写:
- 默认
equals()比较引用地址 → 两个内容相同的对象被视为不同元素,重复添加; - 默认
hashCode()基于内存地址 → 即使内容相同,哈希值也不同 → 删除时无法定位原桶位置,remove()失败; - 后续即使修改对象字段导致其哈希值变化,HashSet 也无法在原桶中找到它,该对象便“永远滞留”,形成内存泄漏。
重写必须遵守的契约
Java 规范明确要求:如果两个对象 equals() 返回 true,它们的 hashCode() 必须返回相同整数。
否则 HashSet 行为不可预测。常见错误包括:
- 只重写
equals()不重写hashCode(); -
hashCode()计算中使用了可变字段(如后面会修改的 name); -
hashCode()使用了 null 未判空的字段,运行时报NullPointerException。
安全重写的实操要点
以 Person 类为例(含 id、name 两个字段):
-
选择不可变字段参与 equals 和 hashCode:推荐用业务主键(如
id),避免用可能被修改的字段(如name); -
重写
equals():先判空和引用相等,再判类型,最后逐个比较关键字段(注意 null 安全); -
重写
hashCode():仅基于与equals()中相同的字段计算,推荐用Objects.hash(id)或手动组合(如31 * result + id); -
对象加入 HashSet 后禁止修改影响哈希值的字段:这是硬性约束,不是建议。若必须改,应先
remove(),再修改,再add()。
典型泄漏场景与修复对比
下面代码会造成泄漏:
Person p = new Person(1001, "Alice");
set.add(p);
p.setName("Bob"); // 修改了 hashCode 计算字段
set.remove(p); // 找不到原桶位置 → 删除失败 → 内存泄漏
修复方式有两种:
-
方案一(推荐):
hashCode()只基于id,name不参与;setName()不影响哈希值; -
方案二(谨慎):若业务强依赖
name判等,则对象一旦加入集合就禁止修改name,或封装为不可变类(final字段 + 无 setter)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











