为什么在添加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 == b
则a.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
作为集合的支持机制。 通常,我们希望调用hashCode
和equals
以确保没有重复项。 但是, 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方法。