为什么覆写了`hashCode()`之后,就一定要覆写 `equals()`

我们为什么需要覆写hashCode()

这里我们可以先假设,如果我们不覆写 hashCode, 会发生什么情况,因为如果不覆写hashCode(),那么默认使用的就是Object#hashCode(),那么每个对象都会得到一个唯一的哈希值,这个看起来正确,但是本身没啥意义,我们举个例子,如果String类不覆写hashCode()来按照字符串的值来产生一个哈希值(散列值),那么就算是相同的字符串锁产生的散列值也是不同的。这会导致什么问题?如果我们使用这样的String类产生的对象来作为hashMap的key的时候,就算是相同的字符串,只要对象不是一个对象,大概率也会被分到不同的槽位上,那么就没法判重了(put 的时候判重就没啥用了),也没法取值(get的时候,)。如果这段不理解,可以看到这段代码:

String a = new String("abc");
String b = new String("abc");
System.out.println(a == b); // false
System.out.println(a.hashCode() == b.hashCode()); // true

如果这里String没有覆写的hashCode,第二个输出也是false,因为JVM根据对象的信息会得到一个唯一的hashCode,有的书上说是对象的在堆的地址,我引用在爆栈上面的回答,这个其实就是一个哈希值而已,本身没啥意义,和地址好像没啥关系。

因为就算是字符串相同的对象,hashcode也不同,这样的hashCode()函数就没用了。那么这样的String类产生的对象用来当作HashMap的key就没有意义了。因为就算相同的字符串,只要对象不同也会得到不同hashCode,那么我们无法根据字符串来取值和设置值了,因为每次根据hashCode获得的槽位也会不同,所以我们要保证对象所产生的hashCode是有意义的,只要对象里面的信息相同,那么hashCode也要相同,所以我们需要覆写hashCode。
比如说我们现在需要一个学生类,里面有学号(Integer类) 和 姓名(Stirng类),如果我们想要这个类在使用哈希算法的数据结构中发挥作用(比如HashMap,HashSet),就要覆写hashCode(),把类里面的成员学号和姓名利用起来。但是我们不是大牛,我们不能保证任何信息不同的对象会得到两个不同的哈希值,因为我们很难设计出完美的哈希(散列)算法。这个时候就会发生“哈希碰撞”。

哈希碰撞

啥是哈希碰撞,就是 两个不同的对象结果得到了同一个哈希值 的这种情况,当然这里的不同的不仅仅是指这两个对象的内存地址不同,而且他们的对象里面的参数这些都不同,比如两个学生对象,他们就是两个不同的人,学号不同,名字有可能也不同。
为什么会发生这种情况,一般来说有两种情况:
1 不同的对象,计算出来的哈希值是相同的。用上面的那个学生类来举例,我们很难设计出“完美哈希函数”,因为只要数据足够多,总有虽然信息不同,但是哈希值相同的情况出现。
2 虽然哈希值不同,但是取余之后得到的槽位的位置是一样的。这个就要举个例子了,HashMap是用一个数组来保存数据的,数组不能无限大,那么每次计算就是用哈希值和数组的长度进行取余计算。
比如一个对象的哈希值是0,而另一个对象的哈希值是16,而此时数组的长度是16,那么这个时候,两个对象就被分配到了同一个槽位,因为取余之后,他们得到的槽位都是0。

如何解决哈希碰撞?

解决哈希碰撞方案比较成熟有两种:(1)链表法;(2)或者开放寻址法。
但是不管是那种解决方法,就需要用到一个核心的函数equals()
因为我们需要在两个对象的哈希值相同的情况下,判断这两个对象的信息是否相同,如果相同,那么就执行相应的操作,该 put 就 put ,该 get 就 get 。equals() 一般来说会做什么操作?比较两个对象的信息是否一致,一般来说用来干这个的,用上面的那个学生类进行举例,这个时候的 equals() 就是负责比较两个对象里的学号是否相同,同时姓名是否相同。如果都相同,那么就是两个相同的信息的对象了。那么这个时候,不管你是get操作还是put操作,都会得到你想要的结果了。
所以,equals()hashCode() 在那些数据结构(HashMapLinkedHashMapConcurrentHashMap )中总是会同时使用到,因为我们基本无法设计出“完美哈希函数”来覆写hashCode函数,所以我们需要覆写equals来帮助判断两个对象的信息是否相同。

总结

相信你看到这里大概明白了这两个覆写的来龙去脉了,之所以我们平时定义的类不少,但是去覆写 Object#equals()Object#hashCode() 这两个方法的机会不多,很大一个原因就是我们很少用我们自己定义的类来当HashMap的key。所以用处就少,是否覆写没差别。
但是如果用到了这些有哈希算法的数据结构上,那么两个同时覆写就是必须的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章