为什么重写 equals 就要重写 hashCode?HashMap 的键为什么不宜修改?
下面是一段教学用的模拟面试。
🧑💻 面试官:为什么重写 equals,通常也要重写 hashCode?
🙋♂️ 我:因为两个相等的对象,hashCode 也应该相等。
🧑💻 面试官:反过来,hashCode 相同,就一定相等吗?
🙋♂️ 我:不是,可能发生哈希冲突。
🧑💻 面试官:一个用户对象作为 HashMap 的键,放进去后修改了参与比较的用户编号。对象没换,为什么反而可能查不到?
哈希容器先缩小查找范围,再判断相等。两套判断必须使用「同一份身份规则」。
面试速答(60 秒版)
equals 定义两个对象在当前类型中算不算相等,hashCode 为哈希容器提供用于定位的整数结果。
两者的契约是:equals 相等的对象必须返回相同 hashCode;hashCode 相同的对象,却不一定 equals 相等,因为哈希冲突是允许的。
所以,如果自定义 equals 让两个不同实例按用户编号相等,就应让 hashCode 也根据一致的身份信息计算。否则,哈希容器可能把本该相等的键分到不同查找位置。
同时,键放入 HashMap 后,不宜修改参与 equals、hashCode 的字段。容器不会因为对象字段变了,就自动重新安排已有映射。工程上通常选择稳定、不可变的身份字段作为键,并保证相等规则对称、传递、一致。

知识点详解:两个“相同用户”,为什么要走同一条查找路?
对象相同,和对象代表同一件事,不是一个概念
假设程序先后读取两次用户资料,得到两个独立对象。两者用户编号都是 42,但它们不是同一个实例。
如果沿用 Object 默认的 equals,判断主要是两个引用是否指向同一个对象;如果类型重写 equals,就可以规定“编号相同代表同一个用户”。
这时我们不是在改变内存中的对象数量,而是在定义业务身份规则。Object API区分了默认实现与相等方法契约。
也不要把 equals 一概说成“比较内容”。哪些内容参与比较,要由当前类型决定。对象中新增一个更新时间,不意味着身份一定跟着变化。
为什么 equals 对了,hashCode 错了,仍然不行?
HashMap 不会在每次查找时,都先拿目标键和所有键做完整比较。它会先根据键的哈希结果缩小查找范围,然后在相关位置进一步检查键是否相等。
假设第一个用户键按编号算出的哈希是 H1,第二个本应相等的用户键却因为沿用另一套计算规则得到 H2。
它们有可能被放到不同查找位置。查找第二个对象时,容器不一定会走到第一个对象所在的位置,于是 equals 再正确也没有机会发挥作用。
所以,相等对象的哈希一致,是让相等判断能够正常参与查找的前提,不是一条为了面试好背而规定的口号。
具体内部还可能有哈希扰动、桶和树等细节,本题不用重述整套 HashMap 源码。先讲清“定位规则与身份规则要一致”就能解释问题。

同一个哈希下面,可以放不同的键
假设用户 42 和用户 73 的哈希恰好相同。
这没有违反契约。一个整数结果无法保证对任意多个对象都唯一,容器需要在发生冲突的位置继续区分它们。
因此,不能用 hashCode 代替 equals,也不能把哈希值当唯一用户编号。哈希相同只说明还需要进一步比较,不说明对象已经相等。
极端地,所有对象都返回同一个哈希,也不一定破坏正确的相等契约,但会损失定位能力,增加冲突处理成本。正确性和分布质量,需要分开讨论。
对象没有换,为什么修改字段后仍可能查不到?
把用户编号 42 作为键放进 Map 时,容器根据当时的哈希安排了映射位置。
后来把该对象编号改成 73。如果新编号让当前哈希变了,下一次用这个对象查找时,查找路径也可能变化,但原来那条映射没有自动搬家。
于是,对象明明还保存在容器里,正常 get 却可能返回空。即使偶然因为碰撞查到了,也不能拿这个结果证明修改是安全的。Map API明确提醒,修改影响键相等比较的值会让行为失去可靠约定。
更稳妥的办法是直接用稳定用户编号作为键,或使用身份字段不可变的键类型。如果业务确实要换身份,就明确删除旧映射,再按新键重新登记,而不是在容器里偷偷改键。

相等规则也不能随意设计
equals 还需要满足自反、对称、传递、一致,以及对 null 返回 false 等约束。
例如父类只比较编号,子类却同时比较编号和地区,可能出现父类认为相等、子类认为不相等的情况。继承结构里的相等规则尤其需要谨慎。
实际写代码时,可以使用 IDE 或语言支持生成成对实现,再核对参与字段是否符合业务身份。生成工具能减少漏项,但不能替你决定“什么时候算同一个人”。
本题属于 Java 对象契约;JavaScript 的 Map 对普通对象键使用身份比较,不会调用你自定义的 Java 风格 equals。Python 有自己的 eq、hash 规则,也不能直接照搬同名方法,因此这里不提供伪对应代码。
面试官继续追问
hashCode 能理解成内存地址吗?
不能作为通用结论。API 约定的是整数哈希,不承诺它等于物理内存地址,也不承诺不同对象永不相同。
只修改不参与 equals 的字段,可以吗?
如果同时不影响 hashCode,也不改变其他键语义,通常不会因这次修改破坏哈希定位。但必须确认字段确实没有间接参与计算;不可变键更容易审查。
record 自动生成的方法,就一定适合做键吗?
还要看组件本身和业务身份。record 的字段绑定受约束,不代表引用到的所有对象都深度不可变;包含可变集合等组件时仍需考虑内容变化。
面试速记卡
- equals:定义当前类型的相等规则,不一概等于比较所有字段。
- 契约:相等必须同哈希,同哈希不保证相等。
- 哈希容器:先定位,再比较,两套规则要一致。
- 可变键:插入后修改身份字段,容器不会自动重新定位。
- 工程选择:使用稳定身份与不可变键,并检查相等规则的完整约束。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
京东 · Java后台 · 校招
什么时候需要重写 hashCode 和 equals?(题意整理)
京东 Java 后台三面凉经 ↗
原帖编辑于 2019-08-23(历史校招面经)