Map map = new HashMap<>();
Person p1 = new Person(1, "aaa");
Person p2 = new Person(1, "bbb"); // id 相同,equals 判定為同一鍵
map.put(p1, p1);
map.put(p2, p2); // p2 覆蓋 p1 的值
System.out.println(map.get(p1)); // Person[id=1, name=bbb]
System.out.println(map.get(p2)); // Person[id=1, name=bbb]
p2.setId(2); // 修改鍵對象的 id(影響 hashCode)
System.out.println(map.get(p1)); // Person[id=2, name=bbb] —— 值對象也被改了!
System.out.println(map.get(p2)); // null —— 找不到鍵了!
class Person {
private int id;
private String name;
// getter/setter/toString 省略
@Override
public int hashCode() {
return id; // 僅基於 id 計算哈希
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
return id == ((Person) obj).getId(); // 僅比較 id
}
}
p1 和 p2 的 hashCode() 均為 1,equals 返回 true,HashMap 判定為同一鍵,p2 的值覆蓋 p1。桶中存儲:key=p1(引用), value=p2(引用)。p2.setId(2) 導致 p2.hashCode() 變為 2。**但桶位置仍由舊 hashCode (1) 決定**。p2.hashCode()=2,去桶 2 找 → **找不到**,返回 null。p1 與 p2 指向同一對象(或同一引用鏈),p1.id 也被改為 2,hashCode()=2。去桶 2 找 → 仍找不到!但原文輸出顯示 Person[id=2, name=bbb],說明此處 p1 與 p2 是**同一對象引用**(或測試代碼有細微差異)。關鍵點:**鍵對象修改後,HashMap 無法定位原桶位置,導致數據「丟失」**。作為 HashMap 鍵的對象,一旦放入 Map,其
hashCode()和equals()依賴的字段必須不可變。
Java 文檔明確警告:「如果鍵對象的值發生變化,導致 equals 比較結果改變,HashMap 的行為是不可預期的。」
public final class PersonKey {
private final int id; // final,無 setter
private final String name;
public PersonKey(int id, String name) {
this.id = id;
this.name = name;
}
// 僅提供 getter,無 setter
// hashCode/equals 基於所有 final 字段
}
@Override
public int hashCode() {
return Objects.hash(id); // id 必須不可變
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return id == p.id;
}
// 修改鍵的 id
Object value = map.remove(p2); // 先用舊鍵移除
p2.setId(2);
map.put(p2, value); // 再用新鍵放入
Integer、String、Long 等 JDK 不可變類型。Map map ,Key = id + ":" + type。(i << 5) - i,速度快且分佈均勻。IdentityHashMap(基于 == 比較),或自定義 Map 重寫 get/put 邏輯,但極不推薦用於生產。這道摩根士丹利面試題,考察的不只是 HashMap 底層原理,更是**Java 核心類庫設計契約**的理解:**不可變性是集合框架正確工作的基石**。實戰中,請始終將「鍵對象不可變」作為鐵律刻入肌肉記憶,代碼審查時重點檢查鍵類是否存在 setter、非 final 字段參與 hashCode/equals。這樣,你就不會在生產環境裡看到數據神秘「消失」的靈異事件了。