欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-14 21:36:05
...

当我们面试的时候,就会有面试官问为什么要重写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,直接对应索引就能找到对应的值,这样的效率就非常高了。
再也不怕面试问为什么要重写 hashcode 和 equals 方法了
但是hash算法也是有弊端的,会出现“hash冲突问题”,例如在存10和11的时候都会计算出hash值位1,在HaspMap中是利用“链表+红黑树”的方案来决解hasp冲突的。如下图所示。
再也不怕面试问为什么要重写 hashcode 和 equals 方法了
它具体的实现是,当一个值进行存储的时候,先进行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肯定是不同的。

举个例子,效果如下图所示。
再也不怕面试问为什么要重写 hashcode 和 equals 方法了
当我们把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有什么特性,这未免太没有新意了吧,这不就换个说法喽。

相关标签: java