爲什麼覆寫了`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。所以用處就少,是否覆寫沒差別。
但是如果用到了這些有哈希算法的數據結構上,那麼兩個同時覆寫就是必須的了。

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