Java 重寫 equals 與 hashCode 的注意事項


為什么重寫 equals 的時候必須重寫 hashCode

大家可能從很多教程中了解到: 

SUN官方的文檔中規定"如果重定義equals方法,就必須重定義hashCode方法,以便用戶可以將對象插入到散列(哈希)表中" 

那么 SUN 公司是出於什么考慮做了這個規定呢? 

在集合框架中的HashSetHashTableHashMap都使用哈希表的形式存儲數據,而hashCode計算出來的哈希碼便是它們的身份證。哈希碼的存在便可以: 

  1. 快速定位對象,提高哈希表集合的性能。
  2. 只有當哈希表中對象的索引即hashCode和對象的屬性即equals同時相等時,才能夠判斷兩個對象相等。
  3. 從上面可以看出,哈希碼主要是為哈希表服務的,其實如果不需要使用哈希表,也可以不重寫hashCode。但是SUN公司應該是出於對程序擴展性的考慮(萬一以后需要將對象放入哈希表集合中),才會規定重寫equals的同時需要重寫hashCode,以避免后續開發不必要的麻煩。

重寫equals的注意事項

Java語言規范要求equals需要具有如下的特性: 

  1. 自反性:對於任何非空引用 x,x.equals() 應該返回 true
  2. 對稱性:對於任何引用 x 和 y,當且僅當 y.equals(x) 返回 truex.equals(y) 也應該返回 true
  3. 傳遞性:對於任何引用 x、y 和 z,如果 x.equals(y)返回 truey.equals(z) 也應返回同樣的結果。
  4. 一致性:如果 x 和 y 引用的對象沒有發生變化,反復調用 x.equals(y) 應該返回同樣的結果。
  5. 對於任意非空引用 x,x.equals(null) 應該返回 false

在對象比較時,我們應該如何編寫出一個符合特性的 equals 方法呢,《Core Java》中提出了如下建議:

  1. 顯式參數命名為 otherObject,稍后將它轉換成另一個叫做 other 的變量。
  2. 檢測 this 與 otherObject 是否引用同一個對象: 

    if (this == otherObject) return true;

    計算這個等式可以避免一個個比較類中的域,實現優化。

  3. 檢測 otherObject 是否為 null,如果為 null,返回 false。進行非空校驗是十分重要的。

  4. 比較 this 與 otherObject 是否屬於同一個類。

    • 如果每個子類都重寫了 equals,使用 getClass 檢驗:
     
              
    if (getClass() != otherObject.getClass()) 
        return false; 
     
              
    • 如果所有子類都使用同一個 equals,就用 instanceof 檢驗:
    if (!(otherObject instanceof ClassName))
        return false;
  5. 將 otherObject 轉換為相應的類型變量。

    ClassName other = (ClassName) otherObject;
  6. 現在可以對所有需要比較的域進行比較了。

    • 基本類型使用 == 比較
    • 對象使用 equals 比較
    • 數組類型的域可以使用靜態方法 Arrays.equals檢測相應數組元素是否相等
    • 如果所有域匹配,則返回 true

注意:子類重寫父類 equals 方法時,必須完全覆蓋父類方法,不能因為類型錯誤或者其他原因定義了一個完全無關的方法。可以使用 @Override 注解對覆蓋父類的方法進行標記,這樣編譯器便會檢測到覆蓋過程中的錯誤。

重寫 hashCode 的注意事項

散列碼(hash code)是由對象導出的一個整型值。散列碼沒有規律,在不同的對象中通過不同的算法生成,Java中生成 hashCode 的策略為(以下說明均摘自 Java API 8):

  • String 類的 hashCode 根據其字符串內容,使用算法計算后返回哈希碼。 

    Returns a hash code for this string. The hash code for a String object is computed as s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

  • Integer 類返回的哈希碼為其包含的整數數值。

    Returns: a hash code value for this object, equal to the primitive int value represented by this Integer object.

  • Object 類的 hashCode 返回對象的內存地址經過處理后的數值。

    Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.

在自己的類中想要重寫 hashCode 的話一般怎么做呢?建議合理地組合實例域的散列碼,讓各個不同對象產生的散列碼更加均勻。例如我們現在有一個 Cat 對象,它有 namesize 和 color 三個不同域,那么可以重寫 hashCode 方法如下:

class Cat {
    ......
    public int hashCode() {
        //hashCode是可以返回負值的
        return 6 * name.hashCode()
            + 8 * new Double(size).hashCode()
            + 10 * color.hashCode();
    }
    ......
}

當然還有更好的做法,我們可以直接調用靜態方法 Objects.hash 並提供多個參數。這個方法會對各個參數調用 Object.hashCode,並組合返回的散列碼。故以上的方法可以縮寫為:

public int hashCode() {
    return Objects.hash(name, size, color);
}

注意: equalshashCode的定義必須一致,兩個對象equalstrue,就必須有相同的hashCode。例如:如果定義的equals比較的是小貓的 name,那么hashCode就需要散列該 name,而不是小貓的 color 或 size。


參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM