Why override hashCode() & equals()?

有这样一个场景:在一个长为n的线性序列中查找某一个数值A,不得不从头到尾遍历,此时的平均查找次数是n/2;但如果将n个数据存放在Hash表中,其平均查找次数接近于1,代价大大地减小。因为在Hash表的存储中,存放其中的数据和其存储位置是存在某种Hash函数关联的。

背景

方便起见,我们假设数据A的Hash函数为A*A%5,而我们设置的Hash表的initialSize是8,如果我们把6和7放入其中,经过Hash函数的计算,6的位置在index=1处,7的位置在index=4处。效果图如下:
在这里插入图片描述
这样存储的好处就显而易见了,如果我们需要查找6这个元素,只要先通过Hash函数计算6的index,然后直接在index=4的位置便可以找到此元素了。

但我们会遇到Hash冲突的问题,当存储7和8时,通过hash计算,得到的index位置是一样的,此时Java的HashMap对象采用的是”链地址法“的解决方案。效果图如下:
在这里插入图片描述

为hash值相同的对象建议一个链表,如果需要查找8,可以沿着index=4的链表依次查找。通过设计合理的hash函数以及hash桶的大小,可以保证链表的长度控制在一个合理的长度里并且做到分布均匀,至于如何使得分布均匀我们后续会讨论。

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

使用HashMap存储自定义类的对象时,如果不重写类的上述方法,会和我们的预期出现很大的偏差。
在这里插入图片描述

从上图可见,我们定义了一个令牌类token,在main方法中new出两个id都为1的token,它俩就好比拥有相同的access权限的令牌。我们创建出一个泛型HashMap,并将token1以及它的value值put进入map中,然后尝试通过token2这个key拿到token1锁存储的value。这就好比用了token1开了access的权限,然后用token2来使用该权限。按道理,通过get(token2)应该会获取"id = 1",但结果居然是null!

原因是没有重写hashCode()和equals()。

当我们向map里put token1时,会调用hashCode()来计算其存储的index位置,由于我们没有重写hashCode(),所以调用的是父类Object的原生hashCode(),其hash值是对象的内存地址。

而token1和token2的内存地址不一样,假设token1的内存地址是100,而token2的内存地址是200,所以get(token2)得到的是null。但如果我们get(token1)的话,很快就能定位到index并得到value。

重写了hashCode()后,会发现get(token2)还是得到了null!
在这里插入图片描述
为什么呢?因为重写后的token2的hashCode确实和token1一样,但token1和token2仅仅只要hash值一样,无法保证token1等于token2,这时需比较equals()。而我们并没有重写equals方法,系统则会调用Object类的equals(),而Object的equals()也是根据内存地址来判断的,所以token1和token2一定不会相等,这就是为啥即使重写了hashCode(),get(token2)也是null的原因。

为了解决这个问题,我们重写equals并得到了下面的结果,只要id相等,两个对象就相等。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值