必须重写 hashcode 和 equals,否则相同业务语义的 key 会被视为不同对象;hashmap 先用 hashcode 定位桶、再用 equals 判等,默认实现仅比较内存地址;需满足三约束:equals 相等则 hashcode 必须相等、hashcode 相等不强制 equals 为 true、参与计算的字段必须不可变。

为什么自定义 Key 必须重写 hashCode 和 equals
不重写会导致相同业务语义的 Key 被当成不同对象存入 HashMap,查不到、删不掉、重复插入。根本原因是 HashMap 查找时先用 hashCode() 定位桶,再用 equals() 比较键是否真正相等;而默认实现来自 Object,只比较内存地址。
重写时必须满足的三个约束条件
违反任一条件都会让 Map 行为异常(如 get() 返回 null 即使键存在):
-
equals()相等的对象,hashCode()必须返回相同整数 -
hashCode()相同,equals()不一定为true(允许哈希碰撞) -
equals()和hashCode()所依赖的字段,必须是不可变的(或确保在作为 Key 期间不修改)
典型错误写法与修复示例(Java)
常见坑:用可变字段参与计算、忽略 null 安全、hashCode 实现太简单(如固定返回 1):
// ❌ 错误:id 可变,且没处理 name 为 null
class BadKey {
String name;
int id;
void setId(int id) { this.id = id; } // 允许修改!
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof BadKey)) return false;
BadKey k = (BadKey) o;
return id == k.id && name.equals(k.name); // name 为 null 时 NPE
}
public int hashCode() { return id; } // 太弱,大量碰撞
}
// ✅ 正确:final 字段 + Objects.equals/hashCode
final class GoodKey {
private final String name;
private final int id;
GoodKey(String name, int id) {
this.name = name;
this.id = id;
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof GoodKey)) return false;
GoodKey k = (GoodKey) o;
return id == k.id && Objects.equals(name, k.name);
}
public int hashCode() {
return Objects.hash(name, id);
}
}
Map 使用中容易被忽略的细节
即使重写正确,仍可能出问题:
- Key 对象一旦放入
HashMap,就绝不能修改参与hashCode/equals的字段,否则该 Key 可能永久“丢失”(桶位置错,get()找不到) - 若用作
ConcurrentHashMap的 Key,还需保证hashCode和equals是线程安全的(通常 final 字段天然满足) - IDE 自动生成的
hashCode/equals一般可靠,但务必检查字段是否都选对、是否含冗余字段、是否漏了final修饰
最危险的不是不会写,而是写了但字段可变,或者改了字段后还试图从 Map 中取值。










