查看原文
其他

再也不怕面试问为什么要重写 hashcode 和 equals 方法了

非科班的科班 非科班的科班 2020-09-07
当我们面试的时候,就会有面试官问为什么要重写hashCode和equals方法,很多人会答不上来,因为确实在实际的工作中重写hashCode和equals方法比较少,一般写一个bean,只要有其属性,get和set方法就完事了,最多也就重写个toString方法,也很少人会去深究这个问题,今天我们就来了解一下为啥要重写hashCode和equals方法。

. 1. HashMap的高效性

在我们常用的数据结构中链表结构,举个例子ArrayList,假如ArrayList里面有n个元素那么它的平均算法的时间消耗就要n/2,n的基数越大,消耗的时长就越大,这个无疑是一种弊端,所以出现了hasp算法。

在HaspMap中是通过hasp算法来进行存储的,每个存在HashMap的元素的存储位置都与hash算法直接挂钩,它在获取元素的时候不用每个每个的一次遍历,而是直接通过生成元素的hash值的方式来找到对应的元素的存储位置,所以算法上是非常高效的。

举个例子吧,假如我的hash函数是x*10%,这里只是举一个例子,真正的hash函数是没那么简单的,这里只是为了简单说明问题。假如我有一个10要存进去,那么计算出来的hash值为1就作为索引下标,然后将值存进对应的位置,然后我又有一个30的值,通过hash计算出来的hash值就是3作为索引下标,将30存进对一个的位置,假如我要取10值,那么将对应的10值传进来,计算出来的hash值就是1,直接对应索引就能找到对应的值,这样的效率就非常高了。



但是hash算法也是有弊端的,会出现“hash冲突问题”,例如在存10和11的时候都会计算出hash值位1,在HaspMap中是利用“链表+红黑树”的方案来决解hasp冲突的。如下图所示。

它具体的实现是,当一个值进行存储的时候,先进行hash值得计算,通过hash值遭到对应的存储位置,然后判断该位置上是否已经存在有值,若是有值,然后进行equals方法得比较,若是equals放比比较位false,新的值就会挂载在旧的值得后面形成一种链表结构,当链表得节点数大于8的时候就会自动转化为红黑树得结构,因为这是考虑到了表的节点数非常多的时候,查询效率就会非常低。

. 2. 重写equals和hashCode方法的原因

上面讲了那么多例子,那么为什么要重写equals和hashCode方法呢,其实在我们用HashMap存储我们自定义对象的时候就要重写equals和hashCode方法,假如不重写得话当我们用HashMap得api操作里面得存储对象的时候,就有可能出现和我们预期的结果不一样的现象,下面来举个例子。

import java.util.HashMap;

class User{
    private Integer id;
private Stirng name;

    public User(Integer id, String name) {
        this.id = id;
this.name=name;
    }

    public Integer getId() {
        return id;
    }

public Integer getName() {
        return name;
    }

//    @Override
//    public int hashCode() {
//        final int prime = 31;
//        int result = 1;
//        result = prime * result + ((id == null) ? 0 : id.hashCode());
//        result = prime * result + ((name == null) ? 0 : name.hashCode());
//        return result;
//    }
//
//    @Override
//    public boolean equals(Object obj) {
//        if (this == obj)
//            return true;
//        if (obj == null)
//            return false;
//        if (getClass() != obj.getClass())
//            return false;
//        User other = (User) obj;
//        if (id == null) {
//            if (other.id != null)
//                return false;
//        } else if (!id.equals(other.id))
//            return false;
//        if (name == null) {
//            if (other.name != null)
//                return false;
//        } else if (!name.equals(other.name))
//            return false;
//        return true;
//    }


}

public class TestHashCode {
    public static void main(String[] args) {
        User user1= new User(1,”zhangsan”);
        User user2= new User(1,”zhangsan”);
        HashMap<User, String> userMap= new HashMap<User, String>();
        userMap.put(user1, "我是第一个用户");
        System.out.println(userMap.get(user2));
    }
}

在mian方法里面定义了两个对象user1和user2,但是user里面的属性对象是一样的,接着创建一个HashMap叫做userMap,将user1存进去,然后用user2来来取值,取出来的值为null。

这里的原因很简单,因为没有重写hash和equals方法,当我们用user2来取直的时候,就会用user2的地址值生成一个hash值,因为user1和user2是完全两个对象所以生成的hash肯定是不同的。

举个例子,效果如下图所示。

当我们把user1存进去的时候,根据地址值生成的索引是1010,user1就存放在对应的位置,接着我们用user2来取值的时候,根据user2来生成的地址值是3030的索引值所对应的位置,所以就返回为null了。

由于user1和user2是完全不同的对象,两份地址值是不一样的,因为没有重写hashCode方法,就会沿用父类Object中的hashCode的方法来生成Hash值,而Object中的hashCode方法生成的hash值是根据对象的地址值直接生成的,所以两个对象的hash就会完全不一样。

当我们把hashCode方法的注释去点后,但是equals的方法不去掉,也进行测试,返回的结果还是为空,这又是为什么呢?原因很简单,因为当我们把user1存进去的时候,hash值是根据user1中的属性生成hash值,因为user1和user2有相同的属性值,所以生成的hash值是一样的,当用user2来取值同样也能找到user1,但是此时还会进行equals方法的比较,而由于没有进行重写equals,所以使用的是Object的equals的方法进行的比较,而Object中的equals方法是直接使用==来比较,实际还是比较的是对象的地址值,这样比较肯定user1不等于user2啦,所以就返回空,这个就是HashMap的完整的取值过程。

这也就是为什么要重写hashCode和equals方法了,在HashMap中存储了自定义对象后,要进行取值的过程,会会进行hashCode和equals方法的比较,假如不重写,那么使用的就是Object中的hashCode和equals中的方法,都是利用地址值来比较,这样就会出问题了,我们更期望的是,当来两个不同的对象,但是他们的属性值是完全一样的,我们认为这是同一个东西,这个才是我们真正期望的。

. 3. 面试问题

其实面试官在问你为什么要重写hashcode或者equals方法的时候,可能是想考你HashMap的底层原理,在我们的项目中经常会用到HashMap,在后台返回前端数据的时候基本都是用HashMap来封装的,大家也知道在面试的时HashMap是百分之90的概率都会问到的,但是直接问你HaspMap有什么特性,这未免太没有新意了吧,这不就换个说法喽。

更多的教程请关注:非科班的科班,我就是我,一个一直在努力前行的码农


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存