2014-10-28 89 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

相关问题