重写 equals 必须重写 hashCode,因为哈希集合依赖 hashCode 分桶、equals 精比;若 equals 为 true 而 hashCode 不同,会导致查找失败或重复插入。

为什么重写 equals 就必须重写 hashCode
因为 Java 集合(比如 HashMap、HashSet)依赖 hashCode 快速分桶,再用 equals 精确比对。如果两个对象 equals 返回 true,但 hashCode 不同,它们就可能被散列到不同桶里,导致 contains 找不到、put 重复插入——这是最典型的“逻辑丢失”问题。
常见错误现象:HashSet.add(obj) 成功两次,HashMap.get(obj) 返回 null 即使键明明存在。
- 只要重写了
equals,就必须重写hashCode,没有例外 - 反之不成立:只重写
hashCode而不重写equals是危险且无意义的 - 两者契约是刚性的:
a.equals(b) == true→a.hashCode() == b.hashCode()(单向蕴含)
怎么写一个安全、可维护的 hashCode 实现
别手算质数、别拼字符串再 hashCode()、别用随机数——这些要么性能差,要么违反契约。JDK 7+ 推荐用 Objects.hash(...),它内部做了空值防护和合理散列,语义清晰又不易出错。
使用场景:实体类、DTO、自定义键类(如 Pair)
- 只参与计算的字段,必须和
equals中用于比较的字段完全一致 - 忽略非业务字段(如
transient、数据库 ID、缓存标记) - 引用类型字段用
Objects.hashCode(field)(自动处理 null),基础类型直接传入Objects.hash(...) - 顺序要和
equals中的字段顺序一致(不是必须,但能降低心智负担)
示例:
public class User {
private String name;
private int age;
private String email;
<pre class="brush:java;toolbar:false;">@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(name, user.name) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(name, age, email); // ✅ 字段顺序、集合范围与 equals 严格对应
}
}
Objects.hash 和手写位运算的取舍
有人觉得手写 result = 31 * result + field.hashCode() 更“底层”或“可控”,但实际上几乎没优势,反而容易翻车。
性能影响:现代 JVM 对 Objects.hash 内联优化极好,和手写差异可忽略;兼容性上,它在 JDK 7+ 全版本行为一致。
- 手写易漏 null 判断(
field.hashCode()对 null 调用会 NPE) - 常数选错(比如用 37 或 61)不会提升质量,31 是历史惯例,有乘法优化,但没必要纠结
-
Objects.hash底层就是用 31 算的,还加了数组封装和空值 guard,更稳 - 如果字段特别多(>10),考虑是否真该放进
hashCode——高频参与散列的字段应精简
哪些字段绝对不能放进 hashCode
核心原则:只要对象加入哈希集合后,该字段可能被修改,就不能参与 hashCode 计算。否则对象位置“锁死”在旧桶里,再也找不到了。
典型错误现象:HashSet.contains(modifiedObj) 返回 false,尽管对象内容仍逻辑相等。
-
volatile或普通可变字段(如状态标志isProcessed) - 延迟加载字段(
lazyInitData),首次调用才赋值,前后hashCode不一致 - 任何 getter 会触发计算或副作用的字段(比如每次调用都 new 个对象)
- 数据库主键 ID(如果对象是新建未持久化的,ID 可能为 0 或 null,之后又被赋值)
如果业务上必须用可变字段做逻辑判断,那就别把它放进 hashCode 和 equals——换用不可变标识(如 UUID 字段)作为哈希依据。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











