本文参考:JAVA为什么两个不同对象的hashCode有可能相同
1. equals()和hashCode()的关系
equals( ):
equals()在java中默认比较地址,是Object类中的方法,故所有对象子类都继承了此方法。但实际应用中常常重写此方法用于比较对象的内容。
hashCode():
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。public int hashCode()返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
既然hashcode为int类型,则最多有2^32个数值,也就自然可能发生冲突(哈希冲突)。
二者关系:
一般来说,
- 相同对象equals()返回true,hashCode()返回值相等
- 不同对象equals()返回false,hashCode()返回值可能相等(哈希冲突)也可能不等
一句话总结,两对象equals时,hashCode一定相等;两对象hashCode相等时,不一定是相同对象。
3. 理解HashSet和HashMap
在讨论为何要同时重写equals()和hashCode()方法前,需要先对HashMap和HashSet有一定了解。
HashMap:
可以从HashMap的数据存取来理解HashMap。要将一个(key, value)数据存入HashMap大致要经过以下几步:
- 使用元素的key调用hashCode()方法获得哈希值,再经过某种哈希算法后获取在数组中的索引(下标)
- 判断此下标位置中是否已存入过此元素
- 若已有此元素,则对key对应的value进行更新
- 若未存入过此元素,则放入(key, value)
可暂时将HashMap理解为一种数组加链表的数据结构,根据元素的key的hashCode确定数组下标,再将(key, value)存入此数组下标对应的链表。
HashSet:
这里先来看一下HashSet的构造方法源码:
public HashSet() {
map = new HashMap<E,Object>();
}
// 构造器中new了一个HashMap。key使用了泛型,value使用Object。
再看下add()方法源码:
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT是一个Object类型的常量,用来当做map的value。也就是说,在HashSet中存储的元素都是HashMap中key,value全部使用Object。
从上述代码可以看出,HashSet的底层其实就是HashMap,相当于只使用了HashMap的一列,也就是key部分来存储数据,value部分全部为一个Object类型的常量PRESENT
。
3. 为何要同时重写equals()和hashCode()
这里还是从哈希表存入数据的角度入手。当由hashCode获取到数组下标后,若当前位置没有元素,则直接放入;若有元素,则采用先比较哈希值再调用equals()的方法判断链表中是否有与待存储元素相同的元素(相同对象的哈希值相等,equals()返回true)。若没有,则将元素放入;若有,则更新元素。
由上述哈希表存入数据的原理可知,两对象只有当hashCode()和equals()返回值都相等时才会被判定为相同对象。所以只有保持equals()和hashCode()的一致性才能在更新哈希表时做到正常更新,而不是把相同的对象再次放入哈希表,造成内存浪费。
4. 一句话总结
同时重写equals()和hashCode()可以避免由于相同元素不同hashCode导致的哈希表中重复元素的问题。