为什么重写 equals 时一定要重写 hashCode?
为什么重写 equals 时一定要重写 hashCode?
zhangchangsheng面试考察点
- 契约理解:面试官不仅仅是想知道 “要一起重写”,更是想考察你是否理解
equals()和hashCode()之间的隐式契约——相等对象必须有相等的哈希码。 - 集合框架原理:这个问题通常和
HashMap、HashSet的工作原理绑定考察,看你是否理解哈希表如何利用这两个方法进行元素的存储和查找。 - 实战踩坑:只重写
equals()不重写hashCode()是 Java 开发中最常见的 bug 之一,考察你是否在生产环境踩过坑。
核心答案
Java 规定:如果两个对象 equals() 返回 true,那么它们的 hashCode() 必须返回相同的值。只重写 equals() 不重写 hashCode() 会违反这个契约,导致 HashMap、HashSet 等哈希集合工作异常。 ****
| 场景 | equals() | hashCode() | 结果 |
|---|---|---|---|
| 都不重写 | 比较地址 | 基于地址 | ✅ 符合契约 |
| 只重写 equals | 比较内容 | 基于地址(不同) | ❌ 违反契约 |
| 两者都重写 | 比较内容 | 基于内容(相同) | ✅ 符合契约 |
深度解析
一、hashCode 与 equals 的契约关系
Java 在 Object 类的规范中定义了 hashCode() 和 equals() 必须遵守的三条契约:
| 契约 | 内容 | 说明 |
|---|---|---|
| 契约一:一致性 | 多次调用同一个对象的 hashCode(),必须返回相同的整数 | 前提:对象参与比较的信息没有被修改 |
| 契约二:相等对象必有相等哈希码 | 如果 equals(a, b) == true,那么 hashCode(a) == hashCode(b) | 最关键的契约,违反它会导致哈希集合(如 HashMap)崩溃 |
| 契约三:不相等对象哈希码可以相同 | 如果 equals(a, b) == false,hashCode 可以相同(哈希冲突) | 但为了性能,应尽量让不同对象产生不同的哈希码 |
上图展示了 hashCode() 和 equals() 的三大契约。其中契约二最关键:
- 如果两个对象
equals()相等,它们的hashCode()必须相等 - 反过来不要求:
hashCode()相等,equals()不一定相等(这就是哈希冲突)
二、HashMap 的查找原理
理解这个问题的关键在于理解 HashMap 如何使用这两个方法:
// 关键:先用 hashCode 定位桶,再用 equals 确认身份 |
上图展示了 HashMap 查找元素的完整流程。关键要点:
- 第一步(定位):先调用
hashCode()计算哈希值,确定元素在哪个桶(bucket)中 - 第二步(确认):遍历桶内的链表/红黑树,用
equals()逐一比对 key - 顺序很重要:先
hashCode定位位置,再equals确认身份
三、翻车现场:只重写 equals 的后果
import java.util.*; |
问题分析:
p1.equals(p2)返回true,说明逻辑上它们是 “同一个人”- 但
p1.hashCode()和p2.hashCode()返回不同的值(基于内存地址) HashSet在添加p2时,根据hashCode找到了不同的桶,没有发现冲突- 结果:两个 “相等” 的对象被存进了
HashSet,破坏了集合的语义
四、正确姿势:同时重写 equals 和 hashCode
import java.util.*; |
五、IDEA 自动生成(最佳实践)
实际开发中,不要手写 equals() 和 hashCode(),使用 IDE 自动生成或 Lombok:
// 方式一:IDEA 自动生成(Alt + Insert → equals() and hashCode()) |
面试高频追问
- 为什么选择 31 作为 hashCode 的乘数?
- 31 是奇质数,能减少哈希冲突
- 31 * i 等价于
(i << 5) - i,可以用位运算优化 - 历史原因:
String的hashCode用的就是 31,形成了惯例
- 只重写 hashCode 不重写 equals 会怎样? 也会出问题!两个
hashCode相等的对象,equals返回false,会导致HashMap中出现大量哈希冲突,链表变长,性能下降(但不会破坏正确性)。 - 两个不相等的对象 hashCode 相同怎么办? 这叫哈希冲突,是允许的。
HashMap会用链表/红黑树处理冲突,用equals()区分不同的 key。
常见面试变体
- 变体一:”
HashMap的put流程是怎样的?” - 变体二:”
HashSet如何保证元素不重复?” - 变体三:”
Objects.hash()方法的实现原理是什么?”
记忆口诀
equals 相等,hashCode 必等; hashCode 相等,equals 未必等; 只重写 equals 不重写 hashCode,HashSet 必翻车。
总结
Java 规定相等对象的 hashCode 必须相等。只重写 equals() 不重写 hashCode() 会违反契约,导致 HashMap、HashSet 等哈希集合无法正确识别重复元素。实际开发中,使用 IDE 自动生成或 Lombok 的 @EqualsAndHashCode 注解,避免手写出错。
声明:本文转载自小哈的博客 《为什么重写 equals 时一定要重写 hashCode?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
