hashCode() 方法

當 Java 類重寫 equals 方法時,它也應該覆蓋 hashCode 方法。如方法合同中所定義 :

  • 每當在執行 Java 應用程式期間多次在同一物件上呼叫它時,hashCode 方法必須始終返回相同的整數,前提是不修改在物件的等比較中使用的資訊。從應用程式的一次執行到同一應用程式的另一次執行,該整數不需要保持一致。
  • 如果兩個物件根據 equals(Object) 方法相等,則在兩個物件中的每一個上呼叫 hashCode 方法必須產生相同的整數結果。
  • 根據 equals(Object) 方法,如果兩個物件不相等則不需要,則在兩個物件中的每一個上呼叫 hashCode 方法必須產生不同的整數結果。但是,程式設計師應該知道為不等物件生成不同的整數結果可能會提高雜湊表的效能。

雜湊程式碼用於雜湊實現,例如 HashMapHashTableHashSethashCode 函式的結果決定了放置物件的儲存桶。如果提供的 hashCode 實現很好,這些雜湊實現會更有效。良好的實施的一個重要特性是 hashCode 值的分佈是均勻的。換句話說,很多例項儲存在同一個儲存桶中的可能性很小。

用於計算雜湊碼值的演算法可以類似於以下內容:

public class Foo {
    private int field1, field2;
    private String field3;

    public Foo(int field1, int field2, String field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3);
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + field1;
        hash = 31 * hash + field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }
}

使用 Arrays.hashCode() 作為捷徑

Version >= Java SE 1.2

在 Java 1.2 及更高版本中,不是開發計算雜湊碼的演算法,而是可以使用 java.util.Arrays#hashCode 通過提供包含欄位值的 Object 或 primitives 陣列來生成:

@Override
public int hashCode() {
    return Arrays.hashCode(new Object[] {field1, field2, field3});
}

Version >= Java SE 7

Java 1.7 引入了 java.util.Objects 類,它提供了一種方便的方法 hash(Object... objects),它根據提供給它的物件的值計算雜湊碼。這種方法就像 java.util.Arrays#hashCode 一樣。

@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3);
}

注意:這種方法效率很低,每次呼叫自定義 hashCode() 方法時都會生成垃圾物件:

  • 建立臨時 Object[]。 (在 Objects.hash() 版本中,陣列由 varargs 機制建立。)
  • 如果任何欄位是基本型別,則必須將它們裝箱並且可以建立更多臨時物件。
  • 必須填充該陣列。
  • 陣列必須通過 Arrays.hashCodeObjects.hash 方法迭代。
  • Object.hashCode() 或者 Objects.hash 必須進行的呼叫(可能)不能內聯。

雜湊碼的內部快取

由於物件的雜湊碼的計算可能很昂貴,因此在第一次計算物件時將雜湊碼值快取在物件中會很有吸引力。例如

public final class ImmutableArray {
    private int[] array;
    private volatile int hash = 0;

    public ImmutableArray(int[] initial) {
        array = initial.clone();
    }

    // Other methods

    @Override
    public boolean equals(Object obj) {
         // ...
    }

    @Override
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            h = Arrays.hashCode(array);
            hash = h;
        }
        return h;
    }
}

這種方法折算(重複)計算雜湊碼的成本與額外欄位的開銷以快取雜湊碼。這種效能優化是否會得到回報將取決於給定物件被雜湊(查詢)的頻率和其他因素。

你還會注意到,如果 ImmutableArray 的真實雜湊碼恰好為零(2 32 中 有一次機會 ),則快取無效。

最後,如果我們正在雜湊的物件是可變的,那麼這種方法更難以正確實現。但是,如果雜湊碼發生變化,則存在更大的問題; 見上面的合同。