我有以下单元测试:我创建自定义类型变量的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);
}
}
答案 0 :(得分:1)
Oracle AbstractMap.Entry
和HashMap.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
。例如,hashCode
为key
,而"x"
是使用value
Variable
创建的name
对象(用于"x"
hashCode
1}})。换句话说,"x".hashCode()
和new Variable("x").hashCode()
相等。
任何value ^ value
等于0.因此,对于两张地图,地图的hashCode
都为0。