[java] hashCode方法的最佳實現



Answers

如果您對dmeister推薦的Effective Java實現感到滿意,則可以使用庫調用,而不是自行調整:

@Override
public int hashCode(){
    return Objects.hashCode(this.firstName, this.lastName);
}

這需要guava( com.google.common.base.Objects.hashCode(...) )或JDK7( java.util.Objects.hash(...) ),但工作方式相同。

Question

我們如何決定集合的hashCode()方法的最佳實現(假設equals方法已被正確覆蓋)?




我在Arrays.deepHashCode(...)使用了一個很小的包裝,因為它處理正確提供的參數

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}



對於一個簡單的類,通常最容易實現的hashCode()基於由equals()實現檢查的類字段。

public class Zam {
    private String foo;
    private String bar;
    private String somethingElse;

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        Zam otherObj = (Zam)obj;

        if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
            if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
                return true;
            }
        }

        return false;
    }

    public int hashCode() {
        return (getFoo() + getBar()).hashCode();
    }

    public String getFoo() {
        return foo;
    }

    public String getBar() {
        return bar;
    }
}

最重要的是保持hashCode()和equals()一致:如果equals()對兩個對象返回true,那麼hashCode()應該返回相同的值。 如果equals()返回false,那麼hashCode()應該返回不同的值。




about8.blogspot.com,你說

如果equals()對兩個對象返回true,那麼hashCode()應該返回相同的值。 如果equals()返回false,那麼hashCode()應該返回不同的值

我不能同意你的看法。 如果兩個對象具有相同的哈希碼,則不一定意味著它們是相同的。

如果A等於B,那麼A.hashcode必須等於B.hascode

如果A.hashcode等於B.hascode,那並不意味著A必須等於B.




儘管這與Android文檔(Wayback Machine)我自己在Github上的代碼有關 ,但它一般適用於Java。 我的答案是dmeister答案的擴展,只是代碼更容易閱讀和理解。

@Override 
public int hashCode() {

    // Start with a non-zero constant. Prime is preferred
    int result = 17;

    // Include a hash for each field.

    // Primatives

    result = 31 * result + (booleanField ? 1 : 0);                   // 1 bit   » 32-bit

    result = 31 * result + byteField;                                // 8 bits  » 32-bit 
    result = 31 * result + charField;                                // 16 bits » 32-bit
    result = 31 * result + shortField;                               // 16 bits » 32-bit
    result = 31 * result + intField;                                 // 32 bits » 32-bit

    result = 31 * result + (int)(longField ^ (longField >>> 32));    // 64 bits » 32-bit

    result = 31 * result + Float.floatToIntBits(floatField);         // 32 bits » 32-bit

    long doubleFieldBits = Double.doubleToLongBits(doubleField);     // 64 bits (double) » 64-bit (long) » 32-bit (int)
    result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));

    // Objects

    result = 31 * result + Arrays.hashCode(arrayField);              // var bits » 32-bit

    result = 31 * result + referenceField.hashCode();                // var bits » 32-bit (non-nullable)   
    result = 31 * result +                                           // var bits » 32-bit (nullable)   
        (nullableReferenceField == null
            ? 0
            : nullableReferenceField.hashCode());

    return result;

}

編輯

通常,當您重寫hashcode(...) ,您還想重寫equals(...) 。 所以對於那些已經或者已經實現了equals ,這裡是我的Github的一個很好的參考......

@Override
public boolean equals(Object o) {

    // Optimization (not required).
    if (this == o) {
        return true;
    }

    // Return false if the other object has the wrong type, interface, or is null.
    if (!(o instanceof MyType)) {
        return false;
    }

    MyType lhs = (MyType) o; // lhs means "left hand side"

            // Primitive fields
    return     booleanField == lhs.booleanField
            && byteField    == lhs.byteField
            && charField    == lhs.charField
            && shortField   == lhs.shortField
            && intField     == lhs.intField
            && longField    == lhs.longField
            && floatField   == lhs.floatField
            && doubleField  == lhs.doubleField

            // Arrays

            && Arrays.equals(arrayField, lhs.arrayField)

            // Objects

            && referenceField.equals(lhs.referenceField)
            && (nullableReferenceField == null
                        ? lhs.nullableReferenceField == null
                        : nullableReferenceField.equals(lhs.nullableReferenceField));
}



如果我正確理解你的問題,你有一個自定義集合類(即一個從Collection接口擴展的新類),並且你想實現hashCode()方法。

如果您的集合類擴展了AbstractList,那麼您不必擔心它,已經有一個equals()和hashCode()的實現,它通過遍歷所有對象並將它們的hashCodes()一起添加來工作。

   public int hashCode() {
      int hashCode = 1;
      Iterator i = iterator();
      while (i.hasNext()) {
        Object obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
      }
  return hashCode;
   }

現在,如果你想要的是計算特定類的哈希碼的最佳方法,我通常使用^(按位排除或)運算符來處理我在equals方法中使用的所有字段:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}



標準實施很薄弱,使用它會導致不必要的衝突。 想像一下

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

現在,

new ListPair(List.of(a), List.of(b, c))

new ListPair(List.of(b), List.of(a, c))

具有相同的hashCode ,即31*(a+b) + c作為List.hashCode使用的乘數在List.hashCode被重用。 顯然,碰撞是不可避免的,但產生不必要的碰撞就是......不必要的。

使用31沒有什麼實際的智能。 為避免信息丟失,乘數必須是奇數(任何偶數乘數至少丟失最重要的位,4的倍數丟失2等)。 任何奇數乘數都可用。 小的乘法器可能會導致更快的計算(JIT可以使用移位和加法),但考慮到現代英特爾/ AMD處理器的乘法延遲只有三個週期,這一點並不重要。 小的乘數也會導緻小的輸入碰撞,這可能是一個問題。

使用素數是毫無意義的,因為素數在環Z /(2 ** 32)中沒有意義。

所以,我建議使用一個隨機選擇的大奇數(隨便選一個素數)。 由於i86 / amd64 CPU可以使用一個較短的指令來匹配單個有符號字節的操作數,因此像109這樣的乘法器具有微小的速度優勢。為了最小化碰撞,請採取類似0x58a54cf5的操作。

在不同的地方使用不同的乘數是有幫助的,但可能不足以證明額外的工作。




當你特別要求集合時,我想添加一個其他答案尚未提及的方面:一旦HashMap被添加到集合中,HashMap不會期望它們的鍵改變它們的哈希碼。 會打敗整個目的...







如果你使用eclipse,你可以使用下面的代碼生成equals()hashCode()

源 - >生成hashCode()和equals()。

使用這個函數,你可以決定你想用哪個字段進行相等和散列碼計算,並且Eclipse會生成相應的方法。




Related