问题:为什么重写equals方法一定要同时重写hashCode方法?
首先抛出答案:这和集合的某些操作有关(比如HashSet的add方法)
HashSet add(E e) 方法如下:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
复制代码
在HashSet的内部维护了一个HashMap,可以看到对于HashSet的add操作委托给了HashMap的put 操作。
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//内部维护的HashMap
private transient HashMap map;
复制代码
继续查看HashMap的put方法如下:
public V put(K key, V value) {
int hash = hash(key);
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;
return oldValue;
}
}
addEntry(hash, key, value, i);
return null;
}
复制代码
我们大致分析一下这段代码逻辑:
- 首先根据添加元素的hash值寻找到可以放置的Entry数组的位置。
- 然后在这个合适的位置上根据Entry链查找是否有equals相同的值,如果有就返回旧值,如果没有就插入这个HashMap。(注意:如果这个Entry链不存在则直接创建entry并插入这个合适的位置,如果entry链只有一个entry节点,那么直接插入)
我们再来看看HashMap的hash方法如下:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
复制代码
我们可以看到h ^= k.hashCode();
这个调用,这也正好是调用了元素的hashCode方法。
重点来了:如果我们不重写hashCode方法会产生什么样的结果?
我们回头看看HashMap的put方法,如果这个时候重复添加具有相同内容的对象,如果不重写hashCode,那么这俩个对象就有可能会在Entry数组的不同位置分别构建entry,然而它们的内容却是一致的,即使你重写了equals方法并不一定能保证HashSet中的元素唯一。
所以,对于集合操作比如Set类型接口,Map类型接口而言,重写了equals方法一定要重写hashCode 保证元素唯一性。
而对于equals使用的准则有:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true, 并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false, 前提是对象上 equals 比较中所用的信息没有被修改。
非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。