必须同时重写equals和hashcode,因为hashset基于hashmap实现:先用hashcode定位桶,再用equals判断相等;仅重写equals会导致相同对象散列到不同桶而重复插入,仅重写hashcode则无法正确识别语义相等对象。

HashSet 要正确存储和查找自定义对象,必须重写 equals 和 hashCode 方法,且两者要保持一致:如果两个对象 equals 返回 true,它们的 hashCode 必须相等;反之不强制,但尽量让不等的对象有不同哈希值,提升性能。
为什么必须同时重写?
HashSet 底层基于 HashMap 实现,插入时先用 hashCode() 确定桶位置,再用 equals() 判断是否已存在。只重写 equals 不重写 hashCode,会导致逻辑上相等的对象被散列到不同桶中,HashSet 会误判为“新元素”,造成重复;只重写 hashCode 不重写 equals,则即使哈希值相同,也无法正确识别语义相等的对象,影响去重和查找。
重写规则与关键点
以一个表示学生的类为例(含 id、name、age):
- 选择参与比较的字段:只选真正决定对象“相等性”的字段。例如 id 是主键,那么 name 和 age 就不该参与 equals/hashCode —— 即使它们变了,只要 id 不变,就仍是同一个学生。
- 保持一致性:equals 中用哪些字段判断相等,hashCode 就必须用完全相同的字段计算哈希值。
- 避免可变字段:不要用可能修改的字段(如后期 setAge() 改变 age),否则对象加入 HashSet 后再改字段,会导致无法被 find/remove(哈希位置变了,但原位置找不到,新位置又没存)。
-
处理 null:用
Objects.equals(a, b)和Objects.hash(...)自动处理 null,安全又简洁。
标准写法示例(推荐使用 Objects 工具类)
<font color="#888">public class Student {
private final Long id; // 建议 final,避免修改导致哈希失效
private final String name;
public Student(Long 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;
Student student = (Student) o;
return Objects.equals(id, student.id) &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}</font>
这样写简洁、健壮、符合规范。IDE(如 IntelliJ)也能一键生成,但务必检查字段选择是否合理。
常见错误提醒
- 在
hashCode里用了字段 A,equals却只比字段 B → 行为不可预测。 - 重写了
equals但没重写hashCode→ 编译不报错,运行时 HashSet 失效。 - 用随机数、当前时间、或非确定性值算 hash → 每次调用结果不同,彻底破坏 HashSet 逻辑。
- 字段类型是数组或自定义对象,却直接用
array.equals()或obj.hashCode(),而没确保其自身也正确实现了这两个方法。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











