banner
lMingyul

lMingyul

记录穿过自己的万物
jike
twitter
github
bilibili

這 2 個對象一樣嗎

在寫業務代碼的時候我們經常需要判斷 2 個 Java 物件是否一樣,常用判斷物件是否相等可以使用 == 、equals、hashcode 這 3 個方法,本文試圖搞清楚這三者的用法。

關係操作符==#

使用 == 判斷 2 個物件是否相等,判斷的是這 2 個物件的地址是否相等。

示例代碼:

輸出結果:

可以看出 jack 和 tom 雖然是 new 新建同一個 Person,但是新建出來的 Person 的物件地址是不一樣的,所以 jack 是不等於 tom 的,可以從打印出來的地址結果看出 jack 與 bob 的物件地址一樣,所以它倆比較的結果為 true


Equals() 方法#

Java 中所有類都有一個父類是 Object 這個類,每個類都會繼承這個類的方法,包括 equals() 方法

Equals () 的本質#

繼承父類的方法後,子類可以對父類的同名方法進行重寫,在沒有對 equals() 方法重寫的情況下,使用 equals 方法判斷 2 個物件是否相等與使用 上面所述的用 == 操作符判斷結果是一樣的,判斷的都是這 2 個物件的地址是否相等

示例代碼:

輸出結果:

其實看一下 equals () 方法的底層源碼,其實使用的就是 == 對 2 個物件進行判斷的

重寫 equals () 方法#

有時候我們不僅是要比較 2 個物件的地址是否一樣,還需要判斷這 2 個物件的內容是否一樣,這個時候就需要對 equals 方法進行重寫

可以對 equals 進行以下的重寫

這樣對物件的判斷就會不一樣,這次判斷的是物件的內容是否一樣

輸出結果:

可以看出,jack 物件實例與 tom 物件實例雖然物件地址不一樣,但是由於我們重寫了 equals () 方法,使得比較的重點是物件的內容,所以比較的結果是 true

其他 equals () 重寫寫法#

有許多其他封裝好的工具包都對equals()進行了重寫

Apache Commons Lang 框架#

equals 與 == 的區別#

  • 對於基本類型,== 判斷兩個值是否相等,基本類型沒有 equals () 方法。
  • 對於引用類型,== 判斷兩個變量是否引用的是同一個物件,而 equals () 沒有重寫時,和== 一樣,重寫之後判斷引用的物件內容是否一樣。

hashCode()方法#

hashCode 也是 Object 類中定義的 方法,也可以比較兩個物件是否相等,方法的返回值是調用物件的哈希值,這個哈希值的類型是 int

hashCode () 的實現#

我們看 Object 類的源碼,可以發現 hashCode () 這個方法沒有具體實現的,因為它是一個本地方法,是使用 C 語言實現的,這個方法返回的哈希值是通過將物件的內存地址轉換為整數得到的。

為什麼需要 hashCode () 方法#

上述說過的 equals 方法可以判斷物件之間是否相等,為什麼還需要 hashCode 方法呢?

在源碼的註釋中提到了這個原因:

  • 支持這個方法是為了讓哈希表受益,比如java.util.HashMap提供的哈希表

我們知道 HashSet 和 HashMap 等集合類在往集合中添加元素的時候,都会進行一個操作:判斷當前需要加到集合的物件是否已經存放在當前集合中,這個時候就涉及到了物件之間的比較

而 HashSet 和 HashMap 都使用了 hashCode () 方法來計算物件應該存儲的位置,因此要將物件添加到這些集合類之前,都需要求出將要存儲 key 的 hashCode 值

以下是 HashMap 源碼中求 key 的 hashCode 值的方法

那為什麼要用 hashCode 方法,而不用 equals 方法呢?

原因是調用 equals 方法更加耗時

下面做了的實驗

輸出結果:

從輸出結果可以看出調用 hashCode 方法幾乎不耗時,因為本質上是比較的是 2 個 int 整型值,所以使用 hashCode 方法比較兩個物件是很快的

為什麼需要 equals () 方法#

既然 hashCode 方法這麼快,為什麼還需要 equals 方法呢?這是因為 hashCode 方法具有局限性

局限性:兩個物件的 hashCode 值相等並不代表兩個物件就相等

這是因為 hashCode 值是通過哈希函數計算出來的,一般的計算過程是:通過對數組長度進行取模,這個的數組可以是內存數組、也可是集合數組,由於長度是有限的,就很難避免每次計算出來的哈希值都不一樣,就會產出 "哈希衝突",越糟糕的哈希算法越容易產生衝突。

哈希衝突對於比較兩個物件是否相等是有影響的,即兩個不同的物件哈希值也可能是相同的。

所以單單通過 hashCode 方法是不夠的,還需要使用 equals 方法進行進一步的判斷

HashSet 在添加元素到集合中的過程中就同時使用到這 2 個方法

  • 添加元素的步驟:
    • 計算物件的 hashCode 值來判斷物件加入的位置
    • 與其他已經加入的物件的 hashCode 值作比較
      • 如果沒有一樣的 hashCode 值,證明物件沒有在當前集合中,因為兩個相等的物件的 hashCode 值一定是相等
      • 如果存在一樣的 hashCode 值,再通過 equals 方法進行進一步判斷
    • 如果物件沒有在當前集合中,就把物件存放進集合

這個過程通過先用 hashCode 方法進行判斷,攔截了很多已經存在於集合中的元素調用 equals 方法的次數,提高程序執行的速度

equals 與 hashCode 的關係#

在《Effective Java》中寫到:

覆蓋 equals 時總要覆蓋 hashCode

在每個重寫了 equals 方法的類中,都必須重寫 hashCode 方法

如果只重寫了 equals,沒有重寫 hashCode,當這個類的物件作為元素加到基於散列的集合(包括 HashMap、HashSet 和 Hashtable)中就會出現問題

示例代碼:

輸出結果:

由於沒有重寫 hashCode 方法,每次創建物件的時候,都会調用 Object 類的 hashCode 方法,該方法會生成不一樣的哈希值

而前面已經提到過,HashSet 判斷一個物件是否存在當前集合,就是根據物件的哈希值判斷的,所以會導致 equals 方法與 hashCode 對比的結果不一樣,導致 HashSet 存在 2 個我們認為重複的物件

所以在重寫 equals 方法時,一定要同時重寫 hashCode 方法

那該如何重寫 hashCode 方法?

  • 重寫應該遵循的原則:
    • 如果物件的 equals 方法中比較時用到的信息沒有被修改的話,那麼多次調用這個物件的 hashCode 方法必須始終返回同一個值
    • 如果 2 個物件 equals 方法比較時相等的,那這 2 個物件的 hashCode 方法都返回同樣的結果

輸出結果:

重寫後兩個物件的 hashCode 值一樣,set 集合元素個數也是我們預想中的結果:1 個元素


參考資料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。