为什么重写equals方法,还必须要重写hashcode方法

一、equals方法和hashcode的关系

根据Object.hashCode的通用约定

  1. 如果两个对象相同(equals方法返回true),那么hashcode也相等。(图1
  2. 如果两个对象的hashcode相等,这两个对象不一定相同,因为可能发生了hash冲突。(图2

 

 啥是hash冲突呢?

举个例子:下图的hash算法为   hashcode = num % 4

 

 

                                                        图1

 

看上图,如果有两个对象都是13(意思也就是指向了同一个对象,那它们的hashcode当然也是相等的啦),所以hashcode的值都是1。(13 % 4 = 1)

 

 

                                                              2

 

上图的两个对象的hashcode的值都是1,但是一个对象是13,一个对象是17,它们发生了hash冲突。

二、什么情况下,需要重写equals方法和hashcode方法?

HashSet为例,它的元素是无序且不可重复的。所以在添加一个元素前,就需要判断集合里是不是已经有了这个元素。

那么该怎么判断呢?

如果用equals方法,那就得遍历整个集合去看一一比较有没有重复,想添加一个元素就需要O(n)的时间复杂度。

如果用hashcode方法,我只需计算出hash值,然后根据hash值去查找是否已经有这个元素了,如果hash算法选的合适,只需要O(1)的时间复杂度(如果hash算法选的不好,会造成多次hash冲突,极端情况下会变成O(n)。此处默认hash算法选的合适,毕竟不是本文要探讨的问题)。

所以通常是先执行hashcode方法,如果一样,再执行equals方法。

 

Object类里,equals方法和hashcode方法都是比较对象的内存地址。但是在HashSet里,我们不想根据内存地址去判断是否相等,想根据key值去判断是否相等,所以我们需要重写。

 

 

 

 

 

 

 一定要同时重写这两个方法吗?

是的,否则这两个方法默认比较内存地址。

我们可以设计一个实验,看看是否必须同时重写这两个方法。

 

 

实验数据:

现在我们有两个人:

Person p1 = new Person("张三");
Person p2 = new Person("张三");

 

实验前提:

1.先执行hashcode方法,如果结果一样,再执行equals方法。

2.这两个方法如果不重写,默认比较内存地址。

 

实验步骤与预测实验结果:

实验1步骤:两个方法都不重写。

预测实验结果:集合里会有两个张三。(因为两个对象的内存不一样)

 

实验2步骤:重写equals方法,不重写hashcode方法。

预测实验结果:集合里会有两个张三。(先执行hashcode方法,但这两个张三的内存地址不一样,hashcode也不一样,所以会有两个张三,不执行equals方法,在hash表的分布如下图)

 

实验3步骤:重写hashcode方法,不重写equals方法。

预测实验结果:集合里会有两个张三。(先重写并执行hashcode方法,发现两个都是张三,hashcode一样,接着执行equals方法,发现内存地址不一样,所以会有两个张三,在hash表的分布如下图)

下图这里出现了hash冲突。

 

实验4步骤:重写两个方法。

预测实验结果:集合里只有1个张三,达到了HashSet不重复的目的。(先重写并执行hashcode方法,发现两个都是张三,hashcode一样,接着重写并执行equals方法,发现两个都是张三,返回true,所以程序确认两个张三其实是同一个人)

 

 

下面做的实验结果和这里的推论一样,不想看可以直接跳到结尾看总结。

 

创建一个Person类:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

    //按照实验要求选择性注释
    /*@Override
    public boolean equals(Object obj) {
        if(obj == null) {
            return false;
        }
        if(obj.getClass() != obj.getClass()){
            return false;
        }

        return ((Person)obj).getName() == getName();
    }*/

    //按照实验要求选择性注释
    /*@Override
    public int hashCode() {
        return getName().hashCode();
    }*/
}

 

 

 

再来个测试类:

import  java.lang.*;
import  java.util.HashSet;
import  java.util.Set;

public class Main {

    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        Person p1 = new Person("张三");
        Person p2 = new Person("张三");
        set.add(p1);
        set.add(p2);
        System.out.println(set);
    }
}

 

实验1:两个方法都不重写。

结果:

 

实验2:重写equals方法,不重写hashcode方法。

结果:

 

实验3:重写hashcode方法,不重写equals方法。

结果:

 

实验4:重写两个方法。

结果:

 

 

三、总结

Q1:什么场景下,需要重写equals方法和hashcode方法?

A1判断两个对象是否相等,不是根据对象的内存地址去判断,而是根据key去判断的时候,就需要重写。

比如HashSet集合。

 

Q2:为什么重写equals方法,还必须要重写hashcode方法?

A2

1.先执行hashcode方法,如果结果一样,再执行equals方法。

 

2.这两个方法如果不重写,默认比较内存地址。

如果只重写其中一个,那么另一个还是会比较内存地址。这样则违背了“不是根据对象的内存地址去判断,而是根据key去判断的时候”的重写前提。

 

 

 

posted @ 2019-10-27 18:14  led二极管  阅读(731)  评论(0编辑  收藏  举报