這個并不是一個通用性編程問題,只屬于在Java領域內專有問題。
要做好心理準備,這是一個復雜類的問題,要解答這個問題,需要梳理清楚兩個函數和其它類之間的關系,并且它們之間的關系有點交織。
equals用法
在 Object 類中包含了 equals() 方法:
public boolean equals(Object obj) {
return (this == obj);
}
說明:
- == 用于比較
變量所對應的內存中所存儲的數值 是否相同,要比較 兩個基本類型的數據(注意是基本類型) 或 兩個引用變量 是否相等。
hashCode用法
在 Object 類中還包含了 hashCode() 方法:
public native int hashCode();
請回答,為什么 Object 類需要一個 hashCode() 方法呢?
在 Java 中,hashCode() 方法的主要作用就是為了配合哈希表使用的。
哈希表(Hash Table),也叫散列表,是一種可以通過關鍵碼值(key-value)直接訪問的數據結構,它最大的特點就是可以快速實現查找、插入和刪除。其中用到的算法叫做哈希,就是把任意長度的輸入,變換成固定長度的輸出,該輸出就是哈希值。像 MD5、SHA1 都用的是哈希算法。
像 Java 中的 HashSet、Hashtable、HashMap 都是基于哈希表的具體實現。其中的 HashMap 就是最典型的代表。
思考一下,假設沒有哈希表,你來設計一個數據結構,它里面存放的數據是不允許有重復的,它要怎么實現呢?
- 使用 equals() 方法進行逐個比較 ?
這種方案當然是可行的。但如果數據量特別特別大,采用 equals() 方法進行逐個對比的效率肯定很低,總結:能解決,但效率不高。 - 最好的解決方案還是使用哈希表。總結:能解決,還效率高。
案例說明:
拿 HashMap 來說吧,當我們要在它里面添加對象時,先調用這個對象的 hashCode() 方法,得到對應的哈希值,然后將哈希值和對象一起放到 HashMap 中。當我們要再添加一個新的對象時:
- 獲取對象的哈希值;
- 和之前已經存在的哈希值進行比較,如果不相等,直接存進去;
- 如果有相等的,再調用 equals() 方法進行對象之間的比較,如果相等,不存了;
- 如果不等,說明哈希沖突了,增加一個鏈表,存放新的對象;
- 如果鏈表的長度大于 8,轉為紅黑樹來處理。
就這么一套下來,調用 equals() 方法的頻率就大大降低了。也就是說,只要哈希算法足夠的高效,把發生哈希沖突的頻率降到最低,哈希表的效率就特別的高。
總結
-
== 用于比較變量所對應的內存中所存儲的數值是否相同,要比較兩個基本類型的數據(注意是基本類型)或兩個 引用變量是否相等,只能用==操作符。
-
equals 比較的是值和地址,如果沒有重寫equals方法,其作用與==相同;
在String類中,重寫了equals方法,比較的是值是否相等; -
hashCode用于散列數據結構中的hash值計算;
邏輯推演:
- equals兩個對象相等,那hashcode一定相等。hashcode相等,不一定是同一個對象(有hash沖突現象);
- hashCode 一般與 equals 一起使用,兩個對象作「相等」比較時,因判斷 hashCode 是判斷 equals 的先決條件.
為什么一個類中需要兩個比較方法
因為重寫的 equals() 里一般比較的比較全面比較復雜,這樣效率就比較低,而利用hashCode()進行對比,則只要生成一個 hash 值進行比較就可以了,效率很高,那么 hashCode() 既然效率這么高為什么還要 equals() 呢?
-
因為 hashCode() 并不是完全可靠,有時候不同的對象他們生成的 hashcode 也會一樣(hash沖突),所以 hashCode()只能說是大部分時候可靠,并不是絕對可靠。
-
equals() 相等的兩個對象他們的 hashCode() 肯定相等,也就是用 equals() 對比是絕對可靠的。
為什么重寫 equals 方法時必須同時重寫 hashCode 方法?
可以先看看Java這B 給出的一些建議,就是事前就規定好了...
public class Object {
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* `java.util.HashMap`.
*
* The general contract of `hashCode` is:
*
* a) Whenever it is invoked on the same object more than once during
* an execution of a Java application, the `hashCode` method must
* consistently return the same integer, provided no information
* used in `equals` comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
*
* b) If two objects are equal according to the `equals(Object)` method,
* then calling the `hashCode` method on each of the two objects must
* produce the same integer result.
*
* c) It is not required that if two objects are unequal according to the
* `equals(Object)` method, then calling the `hashCode` method on each of
* the two objects must produce distinct integer results.
* However, the programmer should be aware that producing distinct integer
* results for unequal objects may improve the performance of hash tables.
*/
@IntrinsicCandidate
public native int hashCode();
/**
* Indicates whether some other object is "equal to" this one.
*
* @apiNote
* It is generally necessary to override the `hashCode` method whenever this
* method is overridden, so as to maintain the general contract for the `hashCode`
* method, which states that equal objects must have equal hash codes.
*/
public boolean equals(Object obj) {
return (this == obj);
}
}
上面介紹了 hashCode 方法注釋上列出的三個通用約定,equals 方法的注釋上也有這么一句話:
“每當重寫 equals 方法時,都需要重寫 hashCode 方法,這樣才沒有破壞 hashCode 方法的通用約定.
即:兩個對象為 Equal 的話(調用 equals 方法為 true), 那么這兩個對象分別調用 hashCode 方法也需要返回相同的哈希值。”
所以只重寫 equals 方法不重寫 hashCode 方法的話,可能會造成兩個對象調用 equals 方法為 true,而 hashCode 值不同的情形,這樣即可能造成異常的行為。
這個情形是什么?
兩個內容相等的Person對象p1和p2的hashCode()不同,是因為在Person類中沒有重寫hashCode()方法,它們使用的是Object類繼承下來的hashCode()方法的默認實現。
在Object類中,hashCode()方法的默認實現是將對象的內存地址值作為哈希碼返回。兩個內容同樣的對象,但是地址會不同,這樣就會返回false,導致異常。
總結:
就是一個前人約定而已。也是為了邏輯的自洽。
Reference
Java hashCode方法深入解析
https://www.javabetter.cn/basic-extra-meal/hashcode.html
Java:為什么重寫 equals 方法時必須同時重寫 hashCode 方法?
https://leileiluoluo.com/posts/always-override-hashcode-when-override-equals.html
浙公網安備 33010602011771號