HashMap为不同的内容产生相同的hashCode

时间:2014-10-28 01:08:00

标签: java hash hashmap

我有以下单元测试:我创建自定义类型变量的2个不同对象。我比较他们的哈希码,它只返回其名称的哈希码,即String.hashCode()。

然后我创建2个HashSets,每个HashSets包含一个变量并比较集合的哈希码。

在这两种情况下,哈希码都不同,正如预期的那样。

但是,如果我创建一个名称为index且值为Variable的HashMap,则断言失败,即它们进行比较。那是为什么?

使用Oracle Java 1.8。

编辑:我可以添加另一个保证:Assert.assertNotEquals(map1, map2);也成立。 此外,我想我正确地解释了这句话:

  

映射的哈希码被定义为哈希码的总和   地图的entrySet()视图中的每个条目。这确保了   m1.equals(m2)意味着任何两个m1.hashCode()== m2.hashCode()   根据总合同的要求,映射m1和m2   是Object.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());
}

这是类Variable的定义。

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);
    }
}

2 个答案:

答案 0 :(得分:1)

Oracle 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个字符的字符串也会有所不同。

答案 1 :(得分:1)

这只是一个巧合。 HashMap.Node的{​​{1}}实施(HashMap#hashCode()使用)

hashCode()

public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } key两者具有相同的value。例如,hashCodekey,而"x"是使用value Variable创建的name对象(用于"x" hashCode 1}})。换句话说,"x".hashCode()new Variable("x").hashCode()相等。

任何value ^ value等于0.因此,对于两张地图,地图的hashCode都为0。