大家好,这里是java研究所。
今天给大家来个经典面试题:重写equals方法时,为什么必须重新hashCode方法?
这2个方法位于Object类中,Object类是所有类的父类,所以如果你的类中没有重写这2个方法时,默认将使用Object中的这2个方法。
equals方法
public boolean equals(Object obj) {
return (this == obj);
}
用来判断2个对象是否相等,使用=来判断的,也就是判断的是引用,当2个对象的内存地址一样的时候才相等。
hashCode方法
public native int hashCode();
一个native方法,用来返回对象的hash码,int类型的。
要搞懂开头的问题,我们需要先了解一下这个2个方法到底有何用?
这个要从HashMap来说起,理解了HashMap的原理之后,问题自然就搞懂了。
HashMap原理
HashMap中用来存储key->value这样的数据,可以通过key在快速在HashMap中获取对应的value。
HashMap中有2个操作我们比较熟悉:put操作和get操作
- put(key,value)操作用来向hashmap中存放元素
- 而get(key)可以通过key获取到对应的value
HashMap内部采用数组加链表的方式来存放数据的,数组的元素是一个链表结构的数据,链表中每个元素中会存储key和value的信息。
来看一下put(key,value)的整个过程:
- 获取key的hashcode:
通过key.hashCode()方法获取int类型的hacode值
- 确定put进来的元素应该放在数组的什么位置:
通过 key.hashCode() & (数组大小 - 1) 来确定数组的位置,也就是key的hashcode对数组长度取模,数组默认长度是16,假设:key的hashcode是1,那么通过:1&(16-1) = 1&15 = 1 得到数组的下标为1,也就是说这个元素会放在数组的第2个位置。
- 用链表干什么?
不同的key,hashcode可能相同,也就是说不同的key,他们可能会放在数组的相同位置,那么如何存放呢?这个地方使用链表将多个元素连接起来,存放多个元素。
put的过程大家知道了,我们再来看看get的过程
-
获取key在数组的什么位置
通过key的hashcode找到元素在数组的什么位置,算法:key.hashCode() & (数组大小 - 1)
-
遍历链表查找元素
取出数组中步骤1位置的元素,这个元素是一个链表结构,沿着链表一个个遍历,遍历的过程会判断链表的当前节点的key是否和get方法传入的key相等,判断key是否相等是通过equals方法判断的,若equals方法返回true了,表示元素找到了
重新equals方法,未重写hashCode方法会出现什么结果?
来个Person类,重写equals方法,当name和age都相等的时候equals方法才返回true,但是没有重写hashcode方法
import java.util.HashMap;
public class Person {
String name; //姓名
Integer age; //年龄
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
Person person = (Person) o;
return name.equals(person.name) && age.equals(person.age);
}
public static void main(String[] args) {
HashMap<Person, String> personMap = new HashMap<>();
//创建person1
Person person1 = new Person("周润发", 50);
personMap.put(person1, "资产10个亿!");
System.out.println("第1次,通过get(person1)方法,获取:年龄50岁的周润发:" + personMap.get(person1));
//创建person2,name和age都和person1中的一样
Person person2 = new Person("周润发", 50);
System.out.println("第2次,通过get(person2)方法,获取:年龄50岁的周润发:" + personMap.get(person2));
System.out.println("person1.equals(person2):" + person1.equals(person2));
System.out.println("person1.hashCode() = " + person1.hashCode());
System.out.println("person2.hashCode() = " + person2.hashCode());
}
}
注意main方法中,创建了2个Person:person1和person2,他们的name和age都是一样的,也就说person1.equals(person2)是相等的,将person1放在了personMap,然后通过personMap.get(person2)来找age为50的name为周润发的,看看能不能找到,上面代码输出:
第1次,通过get(person1)方法,获取:年龄50岁的周润发:资产10个亿!
第2次,通过get(person2)方法,获取:年龄50岁的周润发:null
person1.equals(person2):true
person1.hashCode() = 1725154839
person2.hashCode() = 1670675563
第1次通过person1到map中找到50岁的周润发,而第2次通过person2到map中找不到50岁的周润发,实际map中是存在age=50,name=周润发的元素的,为啥找不到啊?
原因:从最后2行输出中可以看出,person1和person2的hashCode不一样,get方法的第一步会通过hashCode来定位数组的位置,hashcode不同,数组的位置就可以不同了,最终直接导致找不到key对应的元素。
所以如果想找到equals相同的元素,那么这2个元素的hashcode必须一样,这样定位到的数组位置是一样的,才可以找到。
调整一下上面代码,当equals方法相同的时候,hashcode也必须要一样,那么hashcode方法中通也通过name和age组合的方式来得到hashcode的值,如下:
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age.hashCode();
return result;
}
运行输出,这次正常了
第1次,通过get(person1)方法,获取:年龄50岁的周润发:资产10个亿!
第2次,通过get(person2)方法,获取:年龄50岁的周润发:资产10个亿!
person1.equals(person2):true
person1.hashCode() = 671364415
person2.hashCode() = 671364415
到此大家都懂了吧,有收获的帮忙分享一下,后面会分享更多经典面试题,欢迎关注,谢谢!
·END·
扫描二维码 | 关注我们