2012-12-09 32 views
4

我正在使用Java 7,並且我在下面有下面的類。我正確實施了equalshashCode,但問題是equals在下面的主要方法中返回false,但hashCode返回兩個對象的相同哈希碼。我可以多看一些眼睛來看看這個班級,看看我在這裏做錯了什麼嗎?equals和hashCode:是否Objects.hash方法被破壞?

更新:我用我自己的散列函數替換了我稱之爲Objects.hash方法的行:chamorro.hashCode() + english.hashCode() + notes.hashCode()。它返回一個不同的哈希碼,這是兩個對象不同時應該做的hashCodeObjects.hash方法是否被破壞?

您的幫助將不勝感激!

import org.apache.commons.lang3.StringEscapeUtils; 

public class ChamorroEntry { 

    private String chamorro, english, notes; 

    public ChamorroEntry(String chamorro, String english, String notes) { 
    this.chamorro = StringEscapeUtils.unescapeHtml4(chamorro.trim()); 
    this.english = StringEscapeUtils.unescapeHtml4(english.trim()); 
    this.notes = notes.trim(); 
    } 

    @Override 
    public boolean equals(Object object) { 
    if (!(object instanceof ChamorroEntry)) { 
     return false; 
    } 
    if (this == object) { 
     return true; 
    } 
    ChamorroEntry entry = (ChamorroEntry) object; 
    return chamorro.equals(entry.chamorro) && english.equals(entry.english) 
     && notes.equals(entry.notes); 
    } 

    @Override 
    public int hashCode() { 
    return java.util.Objects.hash(chamorro, english, notes); 
    } 

    public static void main(String... args) { 
    ChamorroEntry entry1 = new ChamorroEntry("Åguigan", "Second island south of Saipan. Åguihan.", ""); 
    ChamorroEntry entry2 = new ChamorroEntry("Åguihan", "Second island south of Saipan. Åguigan.", ""); 
    System.err.println(entry1.equals(entry2)); // returns false 
    System.err.println(entry1.hashCode() + "\n" + entry2.hashCode()); // returns same hash code! 
    } 
} 
+2

兩個對象具有相同的哈希代碼,但仍然不相等。所以這個問題不是用'Objects.hash',對吧? –

+0

@RayToal這很好,但很愚蠢。考慮到有40億個可能的值,碰撞應該是非常高的速度,你不應該通過手動嘗試幾個值來碰到它們。由於重複使用相同的乘數是造成這種效率低下的原因,因此'Objects.hash'有點像設計*。 – maaartinus

回答

10

其實,你碰巧觸發純粹的巧合。 :)

Objects.hash恰好通過連續添加每個給定對象的哈希碼,然後將結果乘以31來實現,而String.hashCode與其每個字符都相同。巧合的是,您使用的「英文」字符串的差異出現在字符串末尾的一個偏移量處,因爲它們與「查莫羅」字符串中的差異相同,所以一切都完全消除。恭喜!

嘗試使用其他字符串,並且您可能會發現它按預期工作。正如其他人已經指出的那樣,嚴格來說,這種效應實際上並不是錯誤的,因爲即使它們表示的對象不相等,哈希碼也可能正確地發生衝突。如果有的話,它可能是值得嘗試找到一個更有效的散列,但我認爲它應該在現實情況下是必要的。

5

沒有要求不相等的對象必須有不同的hashCode。預期相等的對象具有相同的hashCode,但不會禁止散列衝突。如果不是非常有用的話,return 1;將是一個完全合法的hashCode實現。

只有32位值得可能的哈希碼,以及無數可能的對象,畢竟:)有時會發生碰撞。

1

兩個不相等的對象沒有必要具有不同的哈希值,重要的是對於兩個相等的對象具有相同的哈希值。

我可以實現hashCode()方法是這樣的:

public int hashCode() { 
    return 5; 
} 

,它將仍然是正確的(但效率不高)。

3

HashCode是32位的int值,總會有碰撞的可能性(兩個對象的散列碼相同),但是它很少/偶然。 你的例子是這樣一個高度巧合的例子之一。這是解釋。

當你調用Objects.hash,它內部有如下邏輯調用Arrays.hashCode()

public static int hashCode(Object a[]) { 
    if (a == null) 
     return 0; 
    int result = 1; 
    for (Object element : a) 
     result = 31 * result + (element == null ? 0 : element.hashCode()); 
    return result; 
} 

爲您3 PARAM的hashCode,會導致到下面:

31 * (31 * (31 *1 +hashOfString1)+hashOfString2) + hashOfString3 

爲了您的第一個對象。個別字符串的哈希值是:

查莫羅 - > 1140493257 英語 - > 1698758127 筆記 - > 0

而對於第二個目的:

查莫羅 - > 1140494218 英文 - > 1698728336 筆記 - > 0

如果您注意到,兩個對象中哈希代碼的前兩個值是不同的。

但是,當計算最終散列碼爲:

int hashCode1 = 31*(31*(31+1140493257) + 1698758127)+0; 
    int hashCode2 = 31*(31*(31+1140494218) + 1698728336)+0; 

巧合它導致成1919283673因爲int被存儲在32位相同的散列碼。

驗證理論的自我是使用下面的代碼段:

public static void main(String... args) { 
    ChamorroEntry entry1 = new ChamorroEntry("Åguigan", 
         "Second island south of Saipan. Åguihan.", ""); 
    ChamorroEntry entry2 = new ChamorroEntry("Åguihan", 
         "Second island south of Saipan. Åguigan.", ""); 
    System.out.println(entry1.equals(entry2)); // returns false 
    System.out.println("Åguigan".hashCode()); 
    System.out.println("Åguihan".hashCode()); 
    System.out.println("Second island south of Saipan. Åguihan.".hashCode()); 
    System.out.println("Second island south of Saipan. Åguigan.".hashCode()); 
    System.out.println("".hashCode()); 
    System.out.println("".hashCode()); 
    int hashCode1 = 31*(31*(31+1140493257) + 1698758127)+0; 
    int hashCode2 = 31*(31*(31+1140494218) + 1698728336)+0; 
    System.out.println(entry1.hashCode() + "\n" + entry2.hashCode()); 
    System.out.println(getHashCode(
        new String[]{entry1.chamorro, entry1.english, entry1.notes}) 
        + "\n" + getHashCode(
        new String[]{entry2.chamorro, entry2.english, entry2.notes})); 
    System.out.println(hashCode1 + "\n" + hashCode2); // returns same hash code! 
    } 

    public static int getHashCode(Object a[]) { 
     if (a == null) 
      return 0; 
     int result = 1; 
     for (Object element : a) 
      result = 31 * result + (element == null ? 0 : element.hashCode()); 
     return result; 
    } 

如果你使用一些不同的字符串參數,希望它會導致成不同的hashCode。