equals 和 hashcode 是协同工作的契约伙伴:hashcode 负责快速定位哈希桶,equals 负责最终确认相等性;hashmap 先用 hashcode 定位桶,再用 equals 判定键是否已存在;hashset 底层基于 hashmap 实现,唯一性依赖该契约;hashtable、linkedhashmap 等同样遵循此机制;未正确重写二者将导致去重、查找失败等静默错误。

Java集合框架中,equals 和 hashCode 不是并列的“两个方法”,而是协同工作的“一对契约伙伴”——hashCode 负责快速定位,equals 负责最终确认。这种分工直接决定了 HashMap、HashSet 等核心集合能否正确去重、查找和存储。
HashMap 的键判断流程:先 hash 后 equals
当你往 HashMap 里 put 一个 key,JVM 实际执行的是三步:
- 调用 key.hashCode(),算出哈希值,再对数组长度取模,确定它该放进哪个“桶”(bucket);
- 如果桶为空,直接存入;
- 如果桶已有元素,就遍历该桶内所有节点,对每个节点的 key 调用 key.equals(传入的 key),只要有一个返回 true,就视为 key 已存在,覆盖 value;否则新增节点。
也就是说,hashCode 是第一道筛选门,equals 是最后一道判决席。缺一不可:没有 hashCode,所有元素挤在一个桶里,退化成链表遍历;只有 hashCode 没有 equals,不同内容对象可能被误判为相同(比如 "Aa" 和 "BB" 哈希值相同但内容不同)。
HashSet 本质是 HashMap 的包装
HashSet 的 add 方法底层调用的是 map.put(e, PRESENT),其中 PRESENT 是一个固定空对象。这意味着:
- Set 的“唯一性”完全由 HashMap 的 key 唯一性机制保障;
- 你往 HashSet 里 add 一个对象,等价于把它作为 key 存进 HashMap;
- 所以,如果自定义类没重写 hashCode 和 equals,两个字段相同的对象会被当成两个不同元素加入 Set,违反业务预期。
Hashtable 和 LinkedHashMap 同样依赖这对组合
虽然 Hashtable 是线程安全的老式实现,LinkedHashMap 维持插入顺序,但它们底层仍基于哈希表结构:
- Hashtable 对 key 和 value 都要求可哈希(value 不参与哈希计算,但 key 必须满足 equals/hashCode 契约);
- LinkedHashMap 在哈希定位基础上加了双向链表,但判断 key 是否重复的逻辑与 HashMap 完全一致;
- 哪怕只是用 Collections.synchronizedSet(new HashSet()) 包一层,底层仍是原始的哈希判断逻辑。
不遵守契约的后果不是报错,而是静默失效
Java 不会在编译或运行时强制检查你是否重写了 hashCode。问题往往在上线后才暴露:
- 用户信息用 User 对象作 Map 的 key,修改姓名后再次 get,返回 null(因为 hashCode 变了,找去了别的桶);
- 订单号封装成 OrderKey 类,只重写了 equals 比较订单 ID,却用默认 hashCode,导致缓存命中率骤降;
- 前端传来的 JSON 解析成对象后放进 Set,相同数据反复出现,排查发现 DTO 类没重写这两个方法。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











