【Effective Java 11】覆蓋 equals 時總要覆蓋 hashCode
阿新 • • 發佈:2022-04-11
1. hashCode 的基本約定
每一個覆蓋了 equals 方法的類中,都必須覆蓋 hashCode 方法。如果不這樣做的話,就會違反 hashCode 的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常運作,這類集合包括 HashMap
和 HashSet
。下面是約定內容:
- 在同一個應用程式中,對於某一個物件,只要物件的 equals 方法的比較操作所用到的資訊沒有修改,那麼對一個物件的多次呼叫,hashCode 方法都必須始終返回一個值。
- 如果兩個物件根據
equals(Object)
相等,則 hashCode 方法產生的結果也必須相等。 - 如果兩個物件根據
equals(Object)
2. 如何編寫 hashCode 雜湊函式
一個好的雜湊函式通常傾向於 “為不相等的物件生成不相等的雜湊碼”。理想情況下,應該把集合不相等的例項均勻地分佈到所有可能的 int 值上。想要完全達到這種理想情況是十分困難的。幸運的是,相對接近這種理想情形並不困難。下面給出一種簡單的解決方法:
- 宣告一個
int
變數並命名result
,將它初始化為物件中第一個關鍵域的雜湊碼c
equals
函式比較的域) - 物件中剩下的每一個關鍵域
f
都完成以下步驟- 為該域計算
int
型別的雜湊碼- 如果該領域是基本型別,則計算
Type.hashCode(f)
。如:Integer.hashCode(f)
- 如果該領域是一個物件引用,並且該類的
equals
方法通過遞迴地呼叫equals
的方式來比較這個域,則同樣為這個域遞迴地呼叫 hashCode。如果需要更加複雜的比較,則為這個域計算一個 “正規化” ,然後針對這個正規化呼叫 hashCode。如果這個域的值為null
,則返回 0 (或者其他某個常數,但通常是0) - 如果該域是一個數組,則要把每一個元素當作單獨的域來處理。對於中間的每一個元素都使用 2.2 的步驟處理。
- 如果該領域是基本型別,則計算
- 按照
result = 31 * result + c
的方式將 2.1 計算的雜湊碼進行合併 - 返回
result
- 為該域計算
注意參與 hashCode 計算的域必須是 equals 關注的域。
3. 編寫 hashCode 雜湊函式時的一些技巧
- Object 物件具有一個名為
hash
的靜態方法,它接受任意數量的物件,返回他們組合的一個雜湊碼。Object.hash(Object ...)
。但由於涉及到陣列建立,以及基本型別的自動裝箱,其效能較低。 - 對於不可變的類,可以在其每一個物件建立的時候就將 hashCode 計算完成並儲存在物件,提升效率。