正確實作 equals() 與 hashCode() 是 Java 開發基礎功力,直接影響 HashMap、HashSet 等集合運作正確性。本文基於 JDK 1.7 原始碼,深度剖析 Object、String 實作細節與重寫規範。
public boolean equals(Object obj) {
return (this == obj); // 僅比較參考位址
}
五大數學特性:
x.equals(x) == truex.equals(y) == y.equals(x)x.equals(y) && y.equals(z) → x.equals(z)x.equals(null) == false關鍵契約:若覆寫 equals(),必須覆寫 hashCode(),確保相等物件擁有相同雜湊碼。
public boolean equals(Object anObject) {
if (this == anObject) return true; // 同參考快速返回
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) return false; // 逐字元比較
i++;
}
return true;
}
}
return false;
}
兩階段比較:1. 參考相等 2. 內容逐字元比較。
public int hashCode() {
int h = hash; // 快取
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; // h = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
}
hash = h;
}
return h;
}
採用 31 為質數乘數 (效能優化:31*i == (i<<5)-i),空字串 hashCode 為 0。
| 場景 | 正確做法 | 錯誤示範 |
|---|---|---|
| 覆寫 equals() | 同時覆寫 hashCode(),使用相同欄位計算 | 只覆寫 equals(),導致 HashMap 找不到相等物件 |
| 欄位選擇 | 包含所有參與 equals 比較的欄位 | equals 比較 id+name,hashCode 只用 id |
| 可變物件 | 不可變欄位計算 hashCode,或確保放入集合後不修改 | List 作為 Key 後修改內容,導致找不到 |
| 效能考量 | 常用欄位優先、避免昂貴運算、考慮快取 | 每次計算 hashCode 都遍歷大型集合 |
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyClass that = (MyClass) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
遵循契約、使用標準庫輔助、保持欄位一致性,是正確實作 equals/hashCode 的關鍵。