这是因为散列的数据结构(HashMap,HashSet等)用到了hashCode和equals方法。
当我们自定义了一个类,例如Student,这个类有一个String类型属性name。
如果我们认为name相同的对象就是同一个人,那么我们就应该重写equals方法
class Student{
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
}
此时我们未重写hashCode方法。
关键点来了:
我们new了两个名字为张飞的对象,我们认为这是同一个人,将s1作为键放入HashMap中,然后我们通过s2取,理论上应该也是能取出来的,因为我们认为是同一个人作为键,但是,却取不出来:
public static void main(String[] args) {
Student s1 = new Student("张飞");
Student s2 = new Student("张飞");
Map hashMap = new HashMap();
hashMap.put(s1,"waqng");
System.out.println(hashMap.get(s2));
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果是首次添加,则初始化数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 位与运算得出索引位置,看此位置是否已有节点,没有最好,那么直接放入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 走到这里说明数组已经完成初始化,并且发生了hash碰撞,需要做进一步判断
else {
Node<K,V> e; K k;
// 这就是咱们所探讨的主题
// 数组的这个位置已经有了节点元素存在,那么进行判断:
// 此位置元素的key的hash值要是与我们新插入的hash值相等,并且key的地址值相等(也就是同一个对象)或者调用了key的equals方法返回值相等,那么我们认为这两个key是一个,则进行值的覆盖,并且返回旧值。(省略了后面源码)
// 拿我们的例子来解释:因为是两个对象,那么地址值肯定不相等了,所以要想让这个if为true,就必须让这两个名为张飞的student对象的hashCode相等,并且equals方法返回true
// 我们重写了equals方法,两个对象名字相同,返回true成立。但是未重写hashCode方法,两个对象的hashCode方法默认是地址值,那么就不会相等了,所以条件不成立,也就是不认为两个“张飞”是同一个key
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
。。。。。。。。
else {
。。。。。。。。
}
if (e != null) { // existing mapping for key
。。。。。。。
}
}
。。。。。。。
return null;
}
如果我们不通过name是否相同来判断是不是一个人,而是单纯通过对象的地址值进行判断,那么就不需要重写equals和hashCode方法