Z
ZHANK
集合框架进阶

equals 与 hashCode

深入理解 equals/hashCode 契约及在集合中的影响

equals 与 hashCode

如果你把对象当 HashMap 的 key 来用,却没正确重写 equalshashCode——那要么 key 永远找不到,要么同样的值存了好几份。这两个方法是 HashMap/HashSet 的地基

学完本章你将: 理解 equals 契约、hashCode 原理、为什么两者必须一起重写。


HashMap 是怎么工作的(为什么需要 hashCode)

put("Alice", 25):
1. 计算 "Alice".hashCode() → 比如 12345
2. 12345 % 数组长度 → 确定了放在哪个"桶"
3. 放入该桶

get("Alice"):
1. 计算 "Alice".hashCode() → 12345
2. 找到对应桶
3. 用 equals() 逐条比对桶里的 key,找到 Alice

所以:hashCode 决定"去哪个桶找",equals 决定"桶里的哪个是你"。缺一不可。


equals 契约 —— 五项铁律

java
public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;           // 1. 同一个引用,肯定相等
        if (!(o instanceof User u)) return false; // 2. 类型不对,肯定不等
        return age == u.age && Objects.equals(name, u.name);
    }
}

必须要遵守的五条规则:

原则含义示例
自反性`x.equals(x)` 永远 true自己和自己的副本
对称性`a.equals(b)` == `b.equals(a)`不能因为你是我朋友,我就不是你朋友
传递性`a.equals(b) && b.equals(c)` → `a.equals(c)`继承关系中容易违反
一致性多次调用结果不变除非数据被修改
null 安全`x.equals(null)` 永远 false不要抛 NPE

hashCode 契约

java
@Override
public int hashCode() {
    return Objects.hash(name, age); // JDK 7+,自动算
}

三条铁律:

  1. equals 相等的两个对象,hashCode 必须相等(HashMap 把他们当同一个 key)
  2. hashCode 相等的两个对象,不一定 equals 相等(hash 碰撞,正常现象)
  3. hashCode 不等的两个对象,equals 一定不等

不重写 hashCode 的经典 bug

java
class BadKey {
    String name;
    // 只重写 equals,没重写 hashCode!
    @Override public boolean equals(Object o) { /* 正确的 */ }
}

Map<BadKey, String> map = new HashMap<>();
BadKey k1 = new BadKey("Alice");
map.put(k1, "数据");
BadKey k2 = new BadKey("Alice");  // 内容相同
System.out.println(map.get(k2));  // null!!!

原因:k1 和 k2 虽然 equals 相等,但 hashCode 不同(继承了 Object 的,基于内存地址),HashMap 去不同的桶里找——根本找不到。

⚠️ 黄金法则:重写 equals 必须重写 hashCode,重写 hashCode 必须重写 equals。

public int hashCode() { return Objects.hash(name, age); // JDK 7+ }