Java 規範定義 equals 與 hashCode 的雙向契約:
a.equals(b) == true,則 a.hashCode() == b.hashCode() 必須成立。hashCode 相等不代表 equals 相等(哈希碰撞)。hashCode() 確定桶位置。equals() 比較。若未重寫 hashCode,邏輯相等的對象可能落入不同桶,導致無法命中,破壞集合語義。
// equals 逐字符比較
public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String) {
String another = (String) anObject;
if (count != another.count) return false;
char[] v1 = value, v2 = another.value;
int i = offset, j = another.offset;
while (count-- != 0) if (v1[i++] != v2[j++]) return false;
return true;
}
return false;
}
// hashCode 多項式滾動哈希
public int hashCode() {
int h = hash; // 緩存
if (h == 0) {
for (int i = 0; i < count; i++) {
h = 31 * h + val[off++];
}
hash = h;
}
return h;
}
相同字符串必然產生相同哈希值,滿足契約。
class Student {
String name; int age;
// 重寫 equals 比較 name+age
// 若不重寫 hashCode -> 繼承 Object.hashCode() (基於內存地址)
}
Set set = new HashSet<>();
set.add(new Student("張三", 20));
set.add(new Student("張三", 20)); // 邏輯相同,但 hashCode 不同
// 結果:Set 包含兩個元素!違反去重預期
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student that = (Student) o;
return age == that.age && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // JDK 7+ 推薦
// 手動實現:31 * name.hashCode() + age
}
| 誤區 | 後果 |
|---|---|
| 只重寫 equals,不重寫 hashCode | 哈希集合失效,邏輯相等對象被視為不同 |
| hashCode 使用可變字段 | 對象入集合後修改字段導致找不到(哈希值變化) |
| equals 比較非業務字段(如 ID)但 hashCode 用其他字段 | 違反契約,集合行為不可預期 |
hashCode 是性能優化的關鍵:將 O(n) 線性比較降為 O(1) 桶定位 + 少量 equals。重寫時必須使用相同的業務字段,確保「equals 相等 ⇒ hashCode 相等」。