面试时经常会被问的一个问题: 重写equals方法时是否需要重写hashCode方法,反过来又是怎样?
这篇文章将对这个问题进行了一个总结和梳理
一、考察的知识点
首先需要明确下这个问题到底在考察我们的什么知识点,涉及到equals方法和hashCode方法的问题其实都是在考察
把某个类的对象作为key存入map集合时的问题。
map集合是基于数组+链表+红黑树实现的,数组中存的是一个个Node节点,Node中有key,value,next等这些属性,
当发生hash冲突时通过next属性Node就会变成一个链表,当链表长度大于8个时就会变成红黑树但红黑树还是基于链表实现的。
使用map集合时有一个约定就是equals方法返回true的两个对象作为key时在map集合中只能存在一个。
当调用put方法往map集合中添加元素时的步骤是这样的:
(1) 根据key的hashCode计算出一个数组下标, 计算时是根据hashCode值和数组容量来计算的,可以简单理解成
hash%(数组容量-1),即求余数,当然map中为了减少hash冲突(不同的hash值计算出相同的数组下标)不是这么简单算的但道理都是一样的,这里我们用取余数来简单说明:相同的hashCode算出的下标肯定一样,不同的hashcode算出的下标有可能一样也有可能不一样。
(2) 看数组下标处有没有元素,如果没有直接创建新Node节点放在此下标处
(2) 如果数组此下标处有元素,就会遍历这个下标上存的链表或者红黑树中的每个元素
(3) 调用每个元素(Node)的key的equals方法和要put的key做比较,如果equals返回true就用要put的value来替换当前这个Node上的value
(4) 直到遍历到最后一个元素也没有equals方法相等的,就新创建一个Node节点添加到原来的链表或者红黑树中,
注意这个添加在jdk7中采用的是头插法,jdk8中采用的是尾插法
二、重写equals是否需要重写hashCode方法
假设现在有一个类Person,我们重写它的equals方法让它始终返回true
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
return true;
}
}
然后尝试创建这个类的对象作为key往map里边放,
public class Test01 {
public static void main(String[] args) {
HashMap<Person,Integer> map = new HashMap<>();
Person p1 = new Person("zs");
Person p2 = new Person("ls");
map.put(p1,1);
map.put(p2,2);
//输出map中的内容
System.out.println(map);
//输出p1和p2equals的结果
System.out.println(p1.equals(p2));
}
}
控制台会输出
{com.lyy.service.Person@53d8d10a=1, com.lyy.service.Person@e9e54c2=2}
true
equals方法相等的key在map中竟然可以存在两个,这就违反了上边提到的map集合的公约,
为什么会这样呢,因为默认的hashcode方法是根据对象的地址值去计算出来的,而p1p2是两个对象地址值不一样所以计算出的hashCode不一样,根据上边put步骤中的(1)可以知道他们put时大概率会算出不同的数组下标然后放进去,走不到调用equals方法的逻辑。
所以就可以知道 当重写一个类的equals方法时就必须重写hashCode方法,保证equals返回true的两个对象它们的
hashCode值也要是相等的。
三、重写hashCode方法是否需要重写equals方法
还是上边那个Person类,这次重写hashCode方法返回一个固定值,
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return 0;
}
}
还是用上边的test方法,当把p1和p2放入map时,因为p1和p2的hashCode是一样的,所以它俩都会放在下标0处,因为equals方法不相等所以不会相互覆盖,会组成一个链表,这种情况是没有问题的。
所以重写hashCode方法时可以不重写equals方法。