Object默认equals用==比较内存地址,hashCode是native方法,通常基于对象内存地址生成;重写equals必须重写hashCode,否则HashMap等集合因先比hash后比equals而失效。

要真正理解 Java 中 equals 与 hashCode 的源码实现方式,不能只看表面签名,得从 Object 类的默认行为、JVM 底层支撑、集合类的实际调用链条三层切入。它们不是孤立方法,而是一套协同工作的契约机制。
Object 类里的原始定义:native 与 == 的真实含义
在 java.lang.Object 源码中:
-
equals(Object obj)的实现就是return this == obj—— 它不比较字段,只判断两个引用是否指向堆中同一块内存地址; -
hashCode()是一个native方法,没有 Java 层源码,但 OpenJDK 注释明确说明:它的返回值“通常基于对象的内存地址生成”,比如 HotSpot VM 实际使用的是对象头中存储的 identity hash code(通过os::random()或地址扰动算法生成),并非直接裸露地址值; - 这两个方法共同构成“身份标识”体系:== 是物理相等,equals 是逻辑相等的起点,hashCode 是该逻辑在散列表中的投影锚点。
HashMap 的 put/contains 过程:看清两者如何被调度
翻看 HashMap.putVal() 源码(JDK 8+),关键路径如下:
- 先调用
key.hashCode(),再对数组长度取模((n - 1) & hash)定位桶位置; - 若桶内已有节点,则逐个调用
e.hash == hash && (e.key == key || key.equals(e.key))判断是否命中; - 注意:先比 hash,再比 equals —— 如果重写了
equals却没重写hashCode,两个逻辑相等的对象 hash 不同,根本不会走到 equals 比较那一步,直接被分到不同桶里。
IDE 自动生成背后的逻辑:为什么用 31 和 17
IntelliJ 或 Eclipse 生成的 hashCode 通常形如:
立即学习“Java免费学习笔记(深入)”;
result = 31 * result + Objects.hashCode(field1);result = 31 * result + Objects.hashCode(field2);
这并非随意设计:
- 31 是质数,能有效减少哈希冲突,且 JVM 会将
31 * i优化为(i (位运算加速); - 初始值 17 也是质数,避免全零字段导致结果恒为 0;
- 所有参与
equals判定的字段,必须也参与hashCode计算 —— 这是契约落地的代码体现,缺一不可。
调试时可验证的关键点
写个简单测试,打断点观察执行流:
- 新建两个字段完全相同的自定义对象,放入
HashSet,再调用set.contains(another); - 不重写任何方法 →
contains返回 false(因默认 hashCode 不同,没进同一个桶); - 只重写
equals→ 依然返回 false(同上,hash 不匹配,跳过 equals); - 同时重写两者 → 返回 true,且断点会停在
equals方法体内,证明 hash 先筛、equals 后判的链路生效。


















