为什么在添加HashSet和hashCode匹配时没有调用equals()?

当我运行此代码时,为什么只调用hashCode()而不是equals方法,而我的hashCode()实现为两个HashSet条目生成相同的hashCode

 import java.util.HashSet; public class Test1 { public static void main(String[] args) { Student st=new Student(89); HashSet st1=new HashSet(); st1.add(st); st1.add(st); System.out.println("Ho size="+st1.size()); } } class Student{ private int name; private int ID; public Student(int iD) { super(); this.ID = iD; } @Override public int hashCode() { System.out.println("Hello-hashcode"); return ID; } @Override public boolean equals(Object obj) { System.out.println("Hello-equals"); if(obj instanceof Student){ if(this.ID==((Student)obj).ID){ return true; } else{ return false; } } return false; } } 

这个输出是:

 Hello-hashcode Hello-hashcode Ho size=1 

哈希集首先检查引用相等性,如果超过,则跳过.equals调用。 这是一个优化并且有效,因为equals的契约指定如果a == ba.equals(b)

我附上了下面的源代码,突出显示了此检查。

如果你改为添加两个相同的参考元素,你会得到你期望的效果:

  HashSet st1=new HashSet(); st1.add(new Student(89)); st1.add(new Student(89)); System.out.println("Ho size="+st1.size()); 

结果是

 $ java Test1 Hello-hashcode Hello-hashcode Hello-equals Ho size=1 

这是来自OpenJDK 7的源代码,指示了相等优化(来自HashMap,HashSet的底层实现):

 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 e = table[i]; e != null; e = e.next) { Object k; // v-- HERE 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; } 

HashSet使用HashMap作为集合的支持机制。 通常,我们希望调用hashCodeequals以确保没有重复项。 但是, put方法(调用private putVal方法来执行实际操作)在源代码中进行优化:

 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 

如果哈希码匹配,它首先检查在调用equals之前键是否相同。 您传递的是同一个Student对象,因此它们已经是== ,所以|| 运算符短路,从不调用equals

如果传入了不同的Student对象但具有相同的ID ,则==将返回false并且将调用equals

在添加和删除元素时,始终在java哈希集合中的hashCode方法之后调用Equals。 原因是,如果某个元素已经存在于指定的存储桶中,那么JVM会检查它是否与它尝试放置的元素相同。

hashcode()和equals()方法

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

查看HashSet的源代码,它使用HashMap进行所有操作,add方法执行put(element, SOME_CONSTANT_OBJECT) 。 以下是JDK 1.6.0_17的put方法的源代码:

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

正如您所看到的,它在使用equals方法之前执行了==比较。 由于您要添加两次对象的相同实例,因此==返回true,并且永远不会调用equals方法。