在 Java 集合框架中,equals() 與 hashCode() 遵循嚴格契約:
若兩個物件根據 equals() 判定相等,則其 hashCode() 必須相同。
反之不成立:hashCode 相同不代表 equals 為 true(哈希碰撞)。
public boolean equals(Object obj) {
return (this == obj); // 記憶體位址比較
}
public native int hashCode(); // 通常基於記憶體位址生成
| 特性 | 定義 | 程式碼驗證 |
|---|---|---|
| 自反性 | x.equals(x) == true | 任何非 null 物件等於自己 |
| 對稱性 | x.equals(y) → y.equals(x) | 雙向一致 |
| 傳遞性 | x.equals(y) ∧ y.equals(z) → x.equals(z) | 鏈式傳遞 |
| 一致性 | 多次調用結果不變 | 前提:比較欄位未修改 |
| 非空性 | x.equals(null) == false | 防禦性程式設計 |
public boolean equals(Object anObject) {
if (this == anObject) return true; // 同一參考快速返回
if (anObject instanceof String) { // 型別檢查
String another = (String) anObject;
if (value.length != another.value.length) return false; // 長度不同直接 false
for (int i = 0; i < value.length; i++) {
if (value[i] != another.value[i]) return false; // 逐字元比較
}
return true;
}
return false;
}
public int hashCode() {
int h = hash; // 快取欄位
if (h == 0 && value.length > 0) {
for (char c : value) {
h = 31 * h + c; // 31 為質數,減少碰撞
}
hash = h;
}
return h;
}
數學公式:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public class Employee {
private String id;
private String name;
private Department dept;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee that = (Employee) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(dept, that.dept);
}
@Override
public int hashCode() {
return Objects.hash(id, name, dept); // Java 7+ 建議用法
}
}
Objects.equals() 與 Objects.hash() 簡化程式碼(Java 7+)EqualsVerifier 庫自動化測試| 集合類型 | 依賴方法 | 違約後果 |
|---|---|---|
| HashMap / HashSet | hashCode → equals | 重複元素、查找失敗、記憶體洩漏 |
| TreeMap / TreeSet | compareTo / Comparator | 不受 hashCode 影響,但需一致性 |
| IdentityHashMap | == (記憶體位址) | 不使用 equals/hashCode |
// 快取 hashCode(適用於不可變物件)
private volatile int hashCache = 0;
@Override
public int hashCode() {
int h = hashCache;
if (h == 0) {
h = computeHashCode(); // 實際計算
hashCache = h;
}
return h;
}