关于HashMap的一个有趣的小问题
最近面试被问到了一个很有意思的问题,是关于HashMap的。之前没看到过类似的问题,感觉很有趣,所以想记录一下。问题很简单,基于一个很简单的常识,即重写equals方法必须同时重写hashCode方法。问题的内容是,假如只重写了equals方法,或者只重写了hashCode方法,分别会出现什么情况?
个人理解HashMap的核心其实就两点,即如何根据key定位bucket,以及如何判断两个key相等。这个问题主要和第二个问题相关,但是涉及的源码很简单,就下面这一句,p是根据hash值定位到的第一个node,put和get是一样的判断逻辑。
p.hash==hash&&(p.key==key||(key!=null&&key.equals(p.key)))
接下来考虑这两个场景,在这两个场景下分析可能出现的一些case。①put之后用等价的key再次put,②put之后用等价的key尝试get。
首先看只重写equals方法的情况。场景①会发生什么情况?场景②会发生什么情况?
等价的key,equals方法必然返回true,但是由于未重写hashCode方法,所以返回的hash值有两种情况,可能相同也可能不同。假如相同,则两个key会冲突到同一个bucket。假如不相同,又有两种情况,可能冲突到同一个bucket,也可能是不同的bucket。所以这里有3种case:
case a:hash值相同
case b:hash值不同但冲突到同一个bucket
case c:hash值不同且bucket不同
场景①(put之后用等价的key再次put)
case a的结果是第二次put的值会替换掉第一次put的值;
case b由于hash值比对不通过,所以会被认为是不同的key,导致在同一bucket中重复插入;
case c都不在一个bucket,肯定也会重复插入。
所以结论是case a勉强能用,b、c都有问题。
场景②(put之后用等价的key尝试get)
case a的结果是get方法可以获取到put插入的数据;
case b由于hash值比对不通过,所以无法正确匹配key,结果就是返回null;
case c定位的bucket都错了,肯定返回null。
所以结论是case a勉强能用,b、c都有问题。
简单总结一下,若只重写equals方法,未重写hashCode方法,只有在两个key的hashCode恰好相等的情况下,才能表现出正常的行为,其他所有的情况都无法正常工作。我们知道两个对象的hashCode重复的概率是极低的,所以能正常工作的可能性也极低。
再看只重写hashCode方法的情况。场景①会发生什么情况?场景②会发生什么情况?
等价的key,equals方法默认比较的是地址,所以必然返回false。但是hashCode方法有重写,所以等价的key会定位到同一个bucket。此时我们再来看以上两种场景会发生什么事情。
场景①(put之后用等价的key再次put)
等价的key冲突到同一个bucket,此时先比较hash值,虽然相等,但是后续的地址比较的equals比较必然为false,所以会重复插入数据,显然是不可用的。
场景②(put之后用等价的key尝试get)
等价的key会定位到同一个bucket,和上面类似,两个key永远无法被判定为相等,所以get必然返回null,所以也是不可用的。
简单总结一下,只重写hashCode方法时,所有case下HashMap都是无法正常工作的。
所以,不管是只重写equals方法,还是只重写hashCode方法,都是无法确保HashMap正常工作的,以上就是可能出现的一些case。我个人的建议是,如果没有必要,使用JDK本身提供的不变类作为key是最好的。非要用自定义类作为key,必须同时正确实现equals方法和hashCode方法。