天天看点

经典面试题:重写equals方法时,为什么必须重写hashCode方法?

大家好,这里是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)的整个过程:

  1. 获取key的hashcode:

通过key.hashCode()方法获取int类型的hacode值

  1. 确定put进来的元素应该放在数组的什么位置:

通过 key.hashCode() & (数组大小 - 1) 来确定数组的位置,也就是key的hashcode对数组长度取模,数组默认长度是16,假设:key的hashcode是1,那么通过:1&(16-1) = 1&15 = 1 得到数组的下标为1,也就是说这个元素会放在数组的第2个位置。

  1. 用链表干什么?

不同的key,hashcode可能相同,也就是说不同的key,他们可能会放在数组的相同位置,那么如何存放呢?这个地方使用链表将多个元素连接起来,存放多个元素。

经典面试题:重写equals方法时,为什么必须重写hashCode方法?

put的过程大家知道了,我们再来看看get的过程

  1. 获取key在数组的什么位置

    通过key的hashcode找到元素在数组的什么位置,算法:key.hashCode() & (数组大小 - 1)

  2. 遍历链表查找元素

    取出数组中步骤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           

到此大家都懂了吧,有收获的帮忙分享一下,后面会分享更多经典面试题,欢迎关注,谢谢!

经典面试题:重写equals方法时,为什么必须重写hashCode方法?

·END·

经典面试题:重写equals方法时,为什么必须重写hashCode方法?

扫描二维码 | 关注我们