1. 比較Java中的對象
在 Java 中,對象可以通過引用和值進行比較。
比較參考資料
如果兩個變量在內存中指向同一個對象,那麼這些變量中存儲的引用是相等的。如果使用相等運算符 ( ==
) 比較這些變量,結果為真,並且該結果有意義。這裡的一切都很簡單。
代碼 | 控制台輸出 |
---|---|
|
|
按價值比較
但是您經常會遇到兩個變量引用兩個相同的不同對象的情況。例如,包含相同文本的兩個不同字符串對象。
要確定不同的對像是否相同,請使用equals()
方法。例如:
代碼 | 控制台輸出 |
---|---|
|
|
該equals
方法不限於String
類。每個班級都有。
甚至是您自己編寫的類,這就是原因。
2.Object
類
Java 中的所有類都繼承該類Object
。Java 的創建者想出了這種方法。
而如果一個類繼承了該類Object
,那麼它就獲得了該類的所有方法Object
。這是繼承的主要結果。
換句話說,每個類都有Object
類的方法,即使他們的代碼沒有提到它們。
這些繼承的方法包括與對像比較相關的方法。這些是equals()
和hashCode()
方法。
代碼 | 實際上,這就是我們將擁有的: |
---|---|
|
|
在上面的示例中,我們創建了一個Person
帶有 name 和 age 參數的簡單類,但沒有一個方法。但是因為所有的類都繼承了這個Object
類,所以這個Person
類自動有兩個方法:
方法 | 描述 |
---|---|
|
比較當前對象和傳遞的對象 |
|
返回當前對象的哈希碼 |
事實證明,絕對每個對像都有equals
方法,不同類型的對象可以相互比較。這樣的代碼將編譯並完美運行。
代碼 | 控制台輸出 |
---|---|
|
|
|
|
3.equals()
方法
該equals()
方法繼承自Object
該類,實現了比較當前對象與傳遞的對象的最簡單算法:它只是比較對對象的引用。
Person
如果您只是比較變量而不是調用方法,您會得到相同的結果equals()
。例子:
代碼 | 控制台輸出 |
---|---|
|
|
當equals
調用該方法時a
,它只是將存儲在變量中的引用a
與存儲在b
變量中的引用進行比較。
但是,類的比較工作不同String
。為什麼?
因為創建該類的人String
編寫了他們自己的方法實現equals()
。
equals()
方法的實現
現在讓我們在類中編寫我們自己的 equals 方法的實現Person
。我們將考慮 4 個主要案例。
equals
方法,它總是以一個Object
對像作為參數
場景 1:調用該方法的同一對equals
像也傳遞給該equals
方法。如果當前對象和傳遞對象的引用相等,則該方法必須返回true
。一個對像等於它自己。
在代碼中它看起來像這樣:
代碼 | 描述 |
---|---|
|
比較參考資料 |
場景 2:null
傳遞給equals
方法——我們沒有什麼可比較的。調用該方法的對象equals
肯定不為null,所以我們需要false
在這種情況下返回。
在代碼中它看起來像這樣:
代碼 | 描述 |
---|---|
|
比較引用 是傳遞過來的對象 null 嗎? |
場景 3:將對不是 a 的對象的引用傳遞Person
給該equals
方法。對像是否Person
等於非Person
對象?這是一個由課程開發者Person
決定的問題,無論他或她想要什麼。
但通常對象必須屬於同一類才能被視為相等。因此,如果類的對像以外的東西Person
被傳遞給我們的 equals 方法,那麼我們將始終返回false
。如何檢查對象的類型?沒錯——通過使用instanceof
運算符。
這是我們的新代碼的樣子:
代碼 | 描述 |
---|---|
|
比較引用 是傳遞過來的對象 null 嗎? 如果傳遞的對像不是 Person |
4.比較兩個Person
對象
我們最終得到了什麼?如果我們已經到達方法的末尾,那麼我們有一個Person
不是的對象引用null
。所以我們將其轉換為a Person
,並比較兩個對象的相關內部數據。這是我們的第四個場景。
代碼 | 描述 |
---|---|
|
比較引用 是傳遞過來的對象 null 嗎? 如果傳遞的對像不是 Person 類型轉換 |
你如何比較兩個Person
對象?如果他們具有相同的名字 ( name
) 和年齡 ( age
),則它們是相等的。最終代碼將如下所示:
代碼 | 描述 |
---|---|
|
比較引用 是傳遞過來的對象 null 嗎? 如果傳遞的對像不是 Person 類型轉換 |
但這還不是全部。
首先,name字段是a String
,所以需要通過調用equals
方法來比較name字段。
this.name.equals(person.name)
其次,該name
字段可能是null
:在這種情況下,您不能調用equals
它。您需要額外檢查null
:
this.name != null && this.name.equals(person.name)
也就是說,如果名稱字段null
在兩個Person
對像中,那麼名稱仍然相同。
第四種情況的代碼可能如下所示:
|
如果年齡不相等, 則立即 return false If this.name 等於null ,使用該方法進行比較沒有意義equals 。這裡要么第二個name 字段等於null ,要么不等於。 使用 方法比較兩個名稱字段 equals 。 |
5.hashCode()
方法
除了equals
旨在對兩個對象的所有字段執行詳細比較的方法之外,還有另一種方法可用於不精確但非常快速的比較:hashCode()
.
想像一下,您正在按字母順序對包含數千個單詞的列表進行排序,並且您需要反复比較單詞對。單詞很長,由很多字母組成。一般來說,這樣的比較會耗費很長時間。
但它可以加速。假設我們有以不同字母開頭的單詞——很明顯它們是不同的。但如果它們以相同的字母開頭,那麼我們還不能說結果會是什麼:這些詞可能相同或不同。
該hashCode()
方法使用類似的原理工作。如果你在一個對像上調用它,它會返回一些數字——類似於單詞中的第一個字母。該數字具有以下屬性:
- 相同的對象總是具有相同的哈希碼
- 不同的對象可以有相同的哈希碼,或者它們的哈希碼可以不同
- 如果對像有不同的哈希碼,那麼對象肯定是不同的
為了更清楚地說明這一點,讓我們用文字重新定義這些屬性:
- 相同的單詞總是有相同的首字母。
- 不同單詞的首字母可以相同,也可以不同
- 如果單詞的首字母不同,則單詞肯定不同
最後一個屬性用於加速對像比較:
首先,計算兩個對象的哈希碼。如果這些hashcode不一樣,那麼對象肯定是不一樣的,沒必要再去比較了。
但是如果哈希碼相同,那麼我們仍然需要使用 equals 方法來比較對象。
6. 代碼契約
上述行為必須由 Java 中的所有類實現。在編譯期間,無法檢查對像是否被正確比較。
Java 程序員有一個普遍的共識,如果他們自己編寫 equals() 方法的實現並因此覆蓋標準實現(在類中),他們也必須按照上述規則編寫自己的方法Object
實現hashCode()
使滿意。
這種安排稱為合同。
如果您只在您的班級中實施該方法equals()
或僅實施該hashCode()
方法,那麼您就嚴重違反了合同(您違反了協議)。不要這樣做。
如果其他程序員使用您的代碼,它可能無法正常工作。此外,您將使用依賴於遵守上述合同的代碼。
所有Java集合在查找元素時,都是先比較對象的hashcode,然後才使用方法進行比較equals
。
這意味著,如果您為自己的類提供了一個equals
方法,但您沒有編寫自己的hashCode()
方法或未正確實現它,那麼集合可能無法與您的對像一起正常工作。
例如,您可能將一個對象添加到列表中,然後使用該方法搜索它contains()
,但集合可能找不到您的對象。
GO TO FULL VERSION