1. 程式人生 > 其它 >【Effective Java 11】覆蓋 equals 時總要覆蓋 hashCode

【Effective Java 11】覆蓋 equals 時總要覆蓋 hashCode

1. hashCode 的基本約定

每一個覆蓋了 equals 方法的類中,都必須覆蓋 hashCode 方法。如果不這樣做的話,就會違反 hashCode 的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常運作,這類集合包括 HashMapHashSet。下面是約定內容:

  • 在同一個應用程式中,對於某一個物件,只要物件的 equals 方法的比較操作所用到的資訊沒有修改,那麼對一個物件的多次呼叫,hashCode 方法都必須始終返回一個值。
  • 如果兩個物件根據 equals(Object)相等,則 hashCode 方法產生的結果也必須相等。
  • 如果兩個物件根據 equals(Object)
    方法比較是不相等的,那麼呼叫這兩個物件中的 hashCode 方法,則不一定要求 hashCode 方法必須產生不同的結果(有可能產生 Hash 碰撞),但是我們應該儘可能做到這點,這影響到該型別的物件在散列表中的效率。

2. 如何編寫 hashCode 雜湊函式

一個好的雜湊函式通常傾向於 “為不相等的物件生成不相等的雜湊碼”。理想情況下,應該把集合不相等的例項均勻地分佈到所有可能的 int 值上。想要完全達到這種理想情況是十分困難的。幸運的是,相對接近這種理想情形並不困難。下面給出一種簡單的解決方法:

  1. 宣告一個 int 變數並命名 result,將它初始化為物件中第一個關鍵域的雜湊碼 c
    ,如步驟2.1 中計算所示(如第 10 條所述,關鍵域是指影響 equals 函式比較的域)
  2. 物件中剩下的每一個關鍵域 f 都完成以下步驟
    1. 為該域計算 int 型別的雜湊碼
      1. 如果該領域是基本型別,則計算 Type.hashCode(f)。如:Integer.hashCode(f)
      2. 如果該領域是一個物件引用,並且該類的 equals 方法通過遞迴地呼叫 equals 的方式來比較這個域,則同樣為這個域遞迴地呼叫 hashCode。如果需要更加複雜的比較,則為這個域計算一個 “正規化” ,然後針對這個正規化呼叫 hashCode。如果這個域的值為 null ,則返回 0 (或者其他某個常數,但通常是0)
      3. 如果該域是一個數組,則要把每一個元素當作單獨的域來處理。對於中間的每一個元素都使用 2.2 的步驟處理。
    2. 按照 result = 31 * result + c 的方式將 2.1 計算的雜湊碼進行合併
    3. 返回 result

注意參與 hashCode 計算的域必須是 equals 關注的域。

3. 編寫 hashCode 雜湊函式時的一些技巧

  • Object 物件具有一個名為 hash 的靜態方法,它接受任意數量的物件,返回他們組合的一個雜湊碼。Object.hash(Object ...)。但由於涉及到陣列建立,以及基本型別的自動裝箱,其效能較低。
  • 對於不可變的類,可以在其每一個物件建立的時候就將 hashCode 計算完成並儲存在物件,提升效率。