Java 如果hashcode不同,为什么HashSet允许相等的项?
该类有一个方法,该方法不是从另一个类继承的。该方法的Javadoc说明如下: 如果指定的元素尚未存在,则将其添加到此集合。更正式地说,如果此集合不包含元素Java 如果hashcode不同,为什么HashSet允许相等的项?,java,hashcode,hashset,Java,Hashcode,Hashset,该类有一个方法,该方法不是从另一个类继承的。该方法的Javadoc说明如下: 如果指定的元素尚未存在,则将其添加到此集合。更正式地说,如果此集合不包含元素e2,则将指定的元素e添加到此集合,从而(e==null?e2==null:e.equals(e2))。如果此集合已经包含元素,则调用将保持集合不变并返回false 换句话说,如果两个对象相等,则不会添加第二个对象,哈希集将保持不变。然而,我发现,如果对象e和e2具有不同的hashcode,则这是不正确的,尽管事实上e.equals(e2)。下
e2
,则将指定的元素e
添加到此集合,从而(e==null?e2==null:e.equals(e2))
。如果此集合已经包含元素,则调用将保持集合不变并返回false
换句话说,如果两个对象相等,则不会添加第二个对象,哈希集将保持不变。然而,我发现,如果对象e
和e2
具有不同的hashcode,则这是不正确的,尽管事实上e.equals(e2)
。下面是一个简单的例子:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
public class BadHashCodeClass {
/**
* A hashcode that will randomly return an integer, so it is unlikely to be the same
*/
@Override
public int hashCode(){
return new Random().nextInt();
}
/**
* An equal method that will always return true
*/
@Override
public boolean equals(Object o){
return true;
}
public static void main(String... args){
HashSet<BadHashCodeClass> hashSet = new HashSet<>();
BadHashCodeClass instance = new BadHashCodeClass();
System.out.println("Instance was added: " + hashSet.add(instance));
System.out.println("Instance was added: " + hashSet.add(instance));
System.out.println("Elements in hashSet: " + hashSet.size());
Iterator<BadHashCodeClass> iterator = hashSet.iterator();
BadHashCodeClass e = iterator.next();
BadHashCodeClass e2 = iterator.next();
System.out.println("Element contains e and e2 such that (e==null ? e2==null : e.equals(e2)): " + (e==null ? e2==null : e.equals(e2)));
}
正如上面的例子清楚地显示的,HashSet能够添加两个元素,其中e.equals(e2)
我将假设这不是Java中的一个bug,并且事实上有一些完全合理的解释来解释为什么会这样。但我不知道到底是什么。我缺少什么?因为hashcode()
的实现非常糟糕
如果您从hashcode()
返回常量值,它将尝试在每个add()
上的每个随机bucket中相等,但不允许您输入任何值
@Override
public int hashCode(){
return new Random().nextInt();
}
每次对同一对象求值时,都会返回不同的has代码。显然你会得到错误的结果
add()函数如下所示
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
而put()是
阅读更多有关短路的内容。由于e.hash==hash
为false,因此不会计算任何其他值。
我希望这会有所帮助。您违反了
equals
/hashCode
的合同,基本上:
从hashCode()
文档:
如果根据equals(Object)方法两个对象相等,那么对两个对象中的每一个调用hashCode方法必须产生相同的整数结果
从开始等于:
请注意,每当重写hashCode
方法时,通常需要重写该方法,以便维护hashCode
方法的一般约定,该约定规定相同的对象必须具有相同的哈希代码
HashSet
依赖于一致地实现equals
和hashCode
,HashSet
名称的HashSet
部分基本上意味着“该类使用hashCode
以提高效率。”如果这两种方法的实现不一致,则所有赌注都被取消
这不应该发生在真实代码中,因为您不应该违反真实代码中的约定…不要求所有元素的哈希代码都不同!只要求两个元素不相等
HashCode首先用于查找对象应该占用的哈希桶。如果hadhcode不同,则假定对象不相等。如果hashcodes相等,则使用equals()
方法确定相等性。哈希代码的使用是一种高效机制
和…
哈希代码实现违反了约定,即除非标识字段的对象发生更改,否则它不应更改 我想你真正想问的是:
“为什么哈希集添加哈希代码不相等的对象,即使它们声称相等?”
我的问题和你发布的问题之间的区别在于,你假设这种行为是一个bug,因此,从这个角度来看,你会感到悲伤。我认为其他海报已经做了充分的工作来解释为什么这不是一个bug,但是他们没有解决潜在的问题
我会在这里尽量做到这一点;我建议您重新表述您的问题,以消除对Java中糟糕文档/bug的指责,这样您就可以更直接地探究为什么会遇到您所看到的行为
文件说明(重点增加):
请注意,每当重写hashCode
方法时,通常需要重写该方法,以便维护hashCode
方法的一般约定,该约定规定相同的对象必须具有相同的哈希代码
equals()
和之间的契约不仅仅是Java规范中令人讨厌的怪癖。它在算法优化方面提供了一些非常有价值的好处。通过假设a.equals(b)
意味着a.hashCode()==b.hashCode()
我们可以进行一些基本的等价性测试,而无需直接调用equals()
。特别是,上面的不变量-a.hashCode()!=b、 hashCode()
表示a.equals(b)
将为false
如果查看(HashSet的内部使用的)代码,您会注意到一个内部静态类条目
,定义如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
...
}
特别要注意的是,如果散列码相等,并且键不是完全相同的对象,则条件函数只调用key.equals(k)
,原因是。根据这些方法的约定,HashMap
跳过此调用应该是安全的。如果对象实现不正确,则HashMap
所做的这些假设将不再正确,并且您将返回不可用的结果,包括集合中的“重复”
请注意,您的声明“HashSet…有一个add(Object o)
方法,它不是从另一个类继承的”并不完全正确。虽然其父类,AbstractSet
没有实现此方法,但父接口确实指定了该方法的约定。Set
接口与哈希无关,只与相等有关,因此它指定了方法i的行为
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
...
}
public V put(K key, V value) {
...
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// Replace existing element and return
}
}
// Insert new element
}