hashCode 是 HashMap 定位键值对的刚性前提,决定“去哪找”;若未重写,默认基于内存地址导致逻辑相同对象哈希值不同,破坏唯一性;必须与 equals 成对重写并遵守契约,且影响 hashCode 的字段须保持不变。

Object 类作为 HashMap 的键时,hashCode 不是“可选优化”,而是定位存取的刚性前提。它不参与最终相等判断,但决定了“去哪找”——没有它,HashMap 就退化成线性查找。
hashCode 是散列表的“地址生成器”
HashMap 底层是数组,每个元素叫一个“桶”。当你 put(key, value),流程是:
- 调用
key.hashCode()得到一个 int 值; - 对该值做扰动(如 JDK8 的
h ^ (h >>> 16)),降低低位碰撞; - 再用
(n - 1) & hash(n 是数组长度,2 的幂)算出数组下标; - 把键值对放进对应桶里(链表或红黑树)。
这个过程完全依赖 hashCode。如果两个逻辑相同的对象返回不同哈希值,它们就会被塞进不同桶——后续 get 时,哪怕 equals 返回 true,也根本不会去另一个桶里翻找。
默认 hashCode 基于内存地址,天然不适合业务语义
Object 类的默认 hashCode 实现通常映射对象内存地址。这意味着:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 两个 new 出来的、字段完全一样的 User 对象,hashCode 不同;
- 它们在 HashMap 中被视为不同 key,导致重复插入、查不到、删不掉;
- 这种行为符合 Object 规范,但违背业务直觉:id 相同的用户,就该是同一个 key。
所以不是“能不能用 Object 实例当 key”,而是“用默认实现会破坏唯一性语义”。
必须和 equals 成对重写,且遵守契约
HashMap 查找时执行两步过滤:
- 第一步比 hashCode:不等 → 直接跳过该桶;
- 第二步才调 equals:仅在 hashCode 相同的桶内逐个比对。
这就要求:
- 如果
a.equals(b) == true,那么a.hashCode() == b.hashCode()必须成立; - 否则 a 和 b 可能落在不同桶,equals 永远没机会被调用;
- 反过来说,hashCode 相同不要求 equals 一定为 true(允许哈希冲突),这是正常设计。
稳定性比“均匀分布”更关键
作为 key 的对象,其影响 hashCode 的字段必须不可变,或至少在放入 HashMap 后不再修改。例如:
- User 类用 id 作 hashCode 依据,但之后又调用
user.setId("new-id"); - 下次 get 时,新 hashCode 导致去错桶,原键值对“消失”了;
- 这不是 bug,是违反了 hashCode 的一致性约定:对象状态未变时,结果必须稳定。

















