2014-10-28 95 views
1

我有以下的單元測試:我創建了2個不同的我自定義類型變量的對象。我比較它們的散列碼,它們只是返回它們名稱的哈希碼,即String.hashCode()。HashMap對於不同的內容產生相同的hashCode

然後我創建了2個HashSets,每個HashSets都持有一個變量並比較集合的哈希碼。

在這兩種情況下,散列碼與預期不同。

但是,如果我創建一個名稱爲索引,變量爲值的HashMap,斷言失敗,即他們比較相同。這是爲什麼?

使用Oracle Java 1.8。

編輯:我可以添加另一個保證:Assert.assertNotEquals(map1, map2);也成立。 此外我認爲正確地解釋這樣的句子:

地圖的哈希碼被定義爲的 在地圖的的entrySet()視圖中的每個條目的散列碼的總和。這確保 m1.equals(m2)意味着根據總體合同 Object.hashCode()的要求,任意兩個 映射m1和m2的m1.hashCode()== m2.hashCode()。 從http://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#hashCode%28%29

@Test 
public void test() { 
    // this assertion holds 
    Assert.assertNotEquals(new Variable("x").hashCode(), new Variable("y").hashCode()); 

    Set<Variable> set1 = new HashSet<>(); 
    set1.add(new Variable("x")); 
    Set<Variable> set2 = new HashSet<>(); 
    set2.add(new Variable("y")); 
    // this assertion also holds 
    Assert.assertNotEquals(set1.hashCode(), set2.hashCode()); 

    HashMap<String, Variable> map1 = new HashMap<>(); 
    map1.put("x", new Variable("x")); 
    HashMap<String, Variable> map2 = new HashMap<>(); 
    map2.put("y", new Variable("y")); 
    // why does this assertion fail? 
    Assert.assertNotEquals(map1.hashCode(), map2.hashCode()); 
} 

兩者下面是類變量的定義。

public class Variable { 
    private String name; 

    public Variable(String name) { 
     this.name = name; 
    } 

    @Override 
    public int hashCode() { 
     return name.hashCode(); 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null || !(obj instanceof Variable)) 
      return false; 
     return name.equals(((Variable) obj).name); 
    } 
} 
+1

爲什麼你認爲散列碼在任何情況下都必須不同? – 2014-10-28 01:12:14

+0

由於第一個斷言成立,我期望第三個斷言成立。同樣的原因爲什麼第二個斷言也成立。 – hooch 2014-10-28 01:13:26

+0

您使用'Variable'的構造函數將傳遞的值賦給'name'嗎? – 2014-10-28 01:18:14

回答

1

甲骨文既AbstractMap.EntryHashMap.Entry的實現定義hashCode爲:

public int hashCode() { 
    return (key == null ? 0 : key.hashCode())^
      (value == null ? 0 : value.hashCode()); 
} 

通知XOR運算符。如果密鑰和值都具有相同的哈希碼,則它們將在XOR時抵消,並且該條目的整體哈希碼將爲0.

這發生在您的代碼中,因爲Variable的哈希碼是與您傳遞給它的字符串相同,並且這些字符串與鍵相同。

值得注意的是,不同的對象不保證有不同的哈希碼。散列碼的唯一保證是相同的對象具有相同的散列碼。如果散列函數好,不相等的對象通常會有不同的散列碼,但這不是保證。

事實證明,這不僅僅是理論上的可能性。在真正的程序中很有可能!

邏輯後續問題是:我該如何避免這種情況? HashMap是我喜歡用名字索引的符號表。而atm我沒有更多有用的類成員變量包含在equals()和hashCode()中。有任何想法嗎?

您可以給Variable一個與內置的String實現不同的哈希碼。一個簡單的方法是使用庫存實施,但change the multiplier from 31到其他一些素數。

例如:

private int hash; 

@Override public int hashCode() { 
    int h = hash; 
    if (h == 0) { 
     int len = name.length(); 
     h = 1; 
     for (int i = 0; i < len; i++) { 
      h = 47*h + name.charAt(i); 
     } 
     hash = h; 
    } 
    return h; 
} 

這是OpenJDK的的String.hashCode()實施的修改版本。我添加了h = 1,所以即使是1個字符的字符串也會不同。

+0

我同意這一點。但是這些元素是兩兩不同的,散列表的散列碼被定義爲元素散列碼的總和,不是嗎?另見我編輯原始問題,我發佈map1.equals(map2)是不正確的。 – hooch 2014-10-28 01:20:32

+0

我明白了。我從來沒有想過這會成爲現實世界中的一個問題。我只是將一個變量的名稱改爲「asdasdfasdfasdf」,現在斷言保持不變。 Idk說什麼..我必須重新設計我的程序的這一方面。 – hooch 2014-10-28 01:28:34

+0

好的邏輯後續問題是:我怎樣才能避免這個問題? HashMap是我喜歡用名字索引的符號表。而atm我沒有更多有用的類成員變量包含在equals()和hashCode()中。有任何想法嗎? – hooch 2014-10-28 01:40:17

1

這只是一個巧合。的hashCode()HashMap.Node實現(HashMap#hashCode()使用)是

public final int hashCode() { 
    return Objects.hashCode(key)^Objects.hashCode(value); 
} 

keyvalue均具有相同hashCode。例如,key"x"value是使用(它用於其hashCode)的name創建的對象Variable。換句話說,"x".hashCode()new Variable("x").hashCode()是相等的。

任何value^value等於0.因此,對於兩個地圖而言,地圖的hashCode都是0。

+0

你們倆同時給出瞭解釋。不能接受你們兩個,對不起。 – hooch 2014-10-28 01:30:49

+0

@hooch無需道歉。只要你掌握了這些概念。我只是想把它放在代碼明智的。 – 2014-10-28 01:32:21

+0

是的,我是通過代碼調試,但也懶得看看我自己寫的代碼。現在我明白了,很明顯爲什麼會發生這種情況。再次感謝。 – hooch 2014-10-28 01:33:44

相關問題