Effective Java Item 11 - 當覆寫 equals 方法時,總要覆寫 hashCode 方法
整理Effective Java書中Item 11: Always override hashCode when you override equals 心得筆記
主旨
當覆寫 equals()
方法時,必須同時覆寫 hashCode()
。如果不這麼做,將違反 hashCode
的契約,並且可能會在 HashMap
和 HashSet
等集合中出現異常行為。
劃重點
hashCode
方法的契約
- 一致性:在同一個應用程式執行期間,多次調用同一物件的
hashCode()
方法,若該物件的equals()
比較資訊未修改,則應該返回相同的結果。 - 相等的物件具有相等的哈希碼:若兩個物件根據
equals()
方法比較為相等,則兩者的hashCode()
必須相同。 - 不相等的物件不必具有不同的哈希碼:如果兩個物件不相等,則不要求它們的
hashCode()
必須不同。不過,為不相等的物件產生不同的哈希碼有助於提高散列表的效能。
為何要遵守這些規範?
- 避免錯誤:若沒有覆寫
hashCode()
,即使兩個物件在equals()
方法中被判定為相等,它們的哈希碼也會不同,這會破壞HashMap
和HashSet
等基於哈希表的集合的運作,可能導致查詢失敗,或是發生錯誤的去重操作。
正確覆寫 hashCode()
方法的範例:
常見錯誤:不遵守 equals()
的契約
以下是一個錯誤範例,這會導致兩個邏輯上相等的物件在 HashMap
中無法正確操作:
問題:
當你使用 PhoneNumber
類的實例作為 HashMap
的鍵時,未覆寫 hashCode()
會導致兩個邏輯上相等的 PhoneNumber
實例擁有不同的哈希碼,從而導致 get
方法無法正確返回已插入的值。
此時,你可能會期望 m.get(new PhoneNumber(707, 867, 5309))
返回 “Jenny”,但實際上,它返回 null
。注意,這裡涉及到兩個 PhoneNumber
實例:一個用來插入到 HashMap
中,另一個相等的實例用來進行(嘗試)檢索。由於 PhoneNumber
類未覆寫 hashCode()
方法,這會導致兩個相等的實例具有不相等的哈希碼,違反了 hashCode
方法的契約。
因此,get
方法查找電話號碼時,可能會在與 put
方法存儲電話號碼時不同的哈希桶中查找,即使這兩個實例可能碰巧分配在同一個哈希桶中,get
方法也幾乎肯定會返回 null
。這是因為 HashMap
優化了每個條目的哈希碼並進行了快取,如果哈希碼不匹配,它不會檢查物件是否相等。
小結
- 覆寫
equals()
時,一定要覆寫hashCode()
,這樣才能確保HashMap
和HashSet
等基於哈希表的集合正確運行。 hashCode()
方法需要遵守其契約,並確保邏輯相等的物件擁有相同的哈希碼。- 在設計時,避免使用簡單的、會使所有物件具有相同哈希碼的方法,這樣會降低散列表的效能,導致最終退化為鏈表查找。
Read other posts