为什么要重写hashCode()和equals()?

 阅读本文大概需要 4.6 分钟

欢迎大家关注我的公众号:神兽学编程

目录

1、前言 

2、Hash算法 

3、为什么需要重写这两个方法


1、前言 

在日常开发中,我们有时会重写hashCode()和equals()方法,但为什么需要重写呢?跟着我的思路来看。 

 

2、Hash算法 

在 你真的了解HashMap吗?(上) 这篇文章中,介绍了一下Hash的基础知识,我们再举个小栗子来了解下Hash。

假设Hash表长度为8,Hash函数为 x*x%5(当然实际函数不会这么简单)。

如果我们要放入6这个元素,先计算6的hash值,结果为1,所以元素6就放到下标为1的位置。

同理,如果要放元素7,hash结果为4,那么元素7就会放到下标为4的位置。

使用Hash的好处非常明显,当我们需要查询某个元素时,只需要通过hash函数获取到元素下标位置,就能找到该元素。

不过可能会遇到Hash冲突问题,比如元素8经过hash计算后得到的结果也为4。对此,HashMap中采用 “链地址法” 解决该问题,封装一个Node采用尾插法连接到尾节点后。

比如我们在存放元素8时,发现4号位置已经被占了,则会封装一个新节点存放元素8,链接在元素7后面,效果如下所示。

 

同理,当我们需要查找元素8时,发现4号位置存放的不是8,则会沿着链表依次查找。

虽然Hash冲突无法避免,但只要Hash函数足够散列,还是能保证链表长度控制在合理的范围。

这里讲解的知识有助于下面了解重写hashCode()方法的重要性。

 

3、为什么需要重写这两个方法

当HashMap的key存放自定义对象时,如果不重写该类的hashCode()和equals()方法,得到的结果会和预期值不同。

我们使用以下代码来进行验证:

public class MyKey {

    private Integer keyId;

    public MyKey(Integer keyId) {
        this.keyId = keyId;
    }

    // @Override
    // public int hashCode() {
    //     return keyId.hashCode();
    // }

    // @Override
    // public boolean equals(Object o) {
    //     if (this == o) return true;
    //     if (o == null || getClass() != o.getClass()) return false;
    //     MyKey key = (MyKey) o;
    //     return keyId.equals(key.keyId);
    // }

    public static void main(String[] args) {
        MyKey key1 = new MyKey(1);
        MyKey key2 = new MyKey(1);
        Map<MyKey, String> map = new HashMap<>(4);
        map.put(key1, "keyId为1");
        System.out.println(map.get(key2));
    }
}

定义了两个MyKey对象,key1和key2,keyId都为1,相当于是同一把锁的两把钥匙。代码中的操作相当于用key1去锁门,用key2去开门,按理key2应该也能打开这把锁,但实际情况并非如此。

 

原因有两个:

①、没有重写MyKey的hashCode()方法;

②、没有重写MyKey的equals()方法;

当我们往HashMap中存放key1时,会先调用key1对象的hashCode()方法计算hash值,随后放入寻址下标位置。

当我们没有重写该对象的hashCode()时,会默认调用Object类的hashCode()方法,而Object类的hashCode()返回的是对象的内存地址。

假设key1的内存地址为0x2501,key2的内存地址为0x3112(不同对象存储的内存位置不同);

那么调用 map.get(key2) ,使用的是key2的内存地址计算的hash值,得到的hash值自然和key1的hash值不相等,因此拿到的值为null。

当放开重写hashCode()方法的注释之后,hashCode()返回的是keyId的hash值,由于key1和key2的keyId相同,所以得到的hash值也相同,这里假设寻址下标位置为3。

但运行结果出乎意料,明明3号位置已经存储了key1,但输出结果还是为null。这是因为没有重写对象的equals()方法,系统调用了Object类的equals()方法,而Object类的equals()方法比较的是对象的内存地址。

key1和key2内存地址自然不相等,所以当重写了equals()方法后,变成比较两个对象中字段值是否相等。而key1的keyId字段值自然和key2的相等,所以当重写了hashCode()和equals()方法之后就能顺利取到值了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值