面试时经常会被问的一个问题: 重写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方法。