必须同时重写equals和hashcode,因为hashset、hashmap先用hashcode定位桶,再用equals精确比较;若仅重写其一,会导致去重失败或查找不到key。

Java 中对象去重,核心依赖 equals 和 hashCode 的协同工作。只重写其中一个,HashSet 去重会失效,HashMap 查不到 key,问题往往悄无声息地发生。
为什么必须两个一起重写
哈希集合(如 HashSet、HashMap 的 key)不是逐个遍历比较,而是分两步:
-
先用
hashCode()算出对象该进哪个“桶”(数组索引) -
再在同一个桶里用
equals()精确判断是否重复
如果两个逻辑相等的对象(比如两个 User(id=100, name="张三"))hashCode 不同,它们会被分到不同桶里,equals 根本不会被调用——去重就失败了。
重写 equals 的关键步骤
一个安全、符合契约的 equals 实现要覆盖五种常见情况:
- 自反性:自己和自己比,返回
true(this == obj) - 非空检查:参数为
null,直接返回false - 类型检查:用
getClass() != obj.getClass()(不用instanceof,避免继承时不对称) - 字段比较:只比决定“业务相等性”的字段(如
id、username),跳过临时计算字段或数据库主键以外的字段 - 空值安全:对可能为
null的引用字段,用Objects.equals(a, b),它自动处理null
重写 hashCode 的基本原则
hashCode 的结果必须和 equals 保持一致:只要 equals 返回 true,hashCode 就必须相同。
- 只参与计算的字段,必须和
equals中用于比较的字段完全一致 - 推荐用
Objects.hash(field1, field2, ...),简洁且空安全 - 避免使用随机数、时间戳、数据库自增 ID 等可变值——否则对象入集合后无法再被找到
- 不要用
String.hashCode()直接拼接,例如name.hashCode() + age容易冲突;Objects.hash内部做了更合理的扰动
实际写法示例(User 类)
假设以 id 和 username 作为唯一标识:
@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 && Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
这样写后,把多个相同 id 和 username 的 User 对象加入 HashSet,最终只会保留一个。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











