Effective Java Item 10 - 覆寫equals方法時必須遵守一般契約
整理Effective Java書中Item 10: Obey the general contract when overriding equals心得筆記
主旨
當覆寫 equals() 方法時,必須遵守其一般契約,這是設計 equals() 方法時的基本要求。如果不遵守這些規範,會導致難以預測的錯誤。
劃重點
equals() 方法的一般契約
- 對稱性:如果
a.equals(b)返回true,那麼b.equals(a)也應該返回true。 - 自反性:對於任何非
null的參照變數a,a.equals(a)必須返回true。 - 傳遞性:如果
a.equals(b)返回true且b.equals(c)返回true,那麼a.equals(c)必須返回true。 - 一致性:如果兩個物件相等,多次調用
a.equals(b)應該返回相同的結果,前提是b沒有改變。 - 對
null的比較:a.equals(null)應該返回false。
為什麼要遵守這些規則?
- 一致性和預測性:如果不遵守
equals()的契約,可能會導致程序行為異常,進而破壞集合的行為,尤其是在使用如HashSet或HashMap這類基於哈希值的容器時,會出現意料之外的結果。 - 容器的正確性:不遵守契約可能會讓集合類型無法正確識別相等物件,從而造成錯誤的去重或插入。
實際範例:正確覆寫 equals() 方法
public class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 先檢查是否是同一個物件
if (o == null || getClass() != o.getClass()) return false; // 處理null和不同類型的情況
User user = (User) o;
return age == user.age && username.equals(user.username); // 比對屬性
}
@Override
public int hashCode() {
return Objects.hash(username, age); // 確保hashCode與equals契約一致
}
}
說明
在這個範例中,equals() 方法遵循了上述所有契約。使用 username 和 age 作為兩個物件相等的標準,確保物件的相等性是基於其重要的屬性進行比較。
覆寫 hashCode() 方法是必要的,因為如果兩個物件相等(即 equals() 返回 true),它們的哈希值也必須相同。這是 HashSet 和 HashMap 正確工作的基礎。若 hashCode() 方法與 equals() 方法不一致,可能會導致容器中的錯誤行為,如無法正確查找或去重相等的物件。
常見錯誤:不遵守 equals() 的契約
以下是一個錯誤範例,這種做法會導致無法正確識別相等的物件,尤其在容器中使用時會出現問題。
public class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return username.equals(user.username); // 錯誤:沒有比對 age 屬性
}
@Override
public int hashCode() {
return Objects.hash(username); // 錯誤:hashCode 沒有考慮 age 屬性
}
}
問題:
這個 equals() 方法沒有比對 age 屬性,導致相同 username 但不同 age 的兩個物件被認為是相等的。
同樣,hashCode() 也未能遵守契約,僅根據 username 計算哈希值,這將導致哈希衝突,影響如 HashSet 和 HashMap 這類基於哈希值的容器的正確性。
小結
覆寫 equals() 方法時,一定要遵守其一般契約,確保方法正確運行,並避免容器中的錯誤行為。
同時,記得覆寫 hashCode() 方法,以確保它與 equals() 一致,保持 HashSet 和 HashMap 的正確性。
Read other posts