在不少場景下,須要斷定兩個對象是否 「相等」,例如:判斷某個Collection 中是否包含特定元素。
==和equals()有和區別?如何爲自定義 ADT正確實現equals()?html
==
和 equals()
。==
是引用等價性 ;而equals()
是對象等價性。
==
比較的是索引。更準確的說,它測試的是指向相等(referential equality)。若是兩個索引指向同一塊存儲區域,那它們就是==的。對於咱們以前提到過的快照圖來講,==
就意味着它們的箭頭指向同一個對象。equals()
操做比較的是對象的內容,換句話說,它測試的是對象值相等(object equality)。e在每個ADT中,quals操做必須合理定義。Java中的數據類型,可分爲兩類: java
嚴格來講,咱們能夠從三個角度定義相等:ide
以上兩種角度/定義其實是同樣的,經過等價關係咱們能夠構建一個抽象函數(譯者注:就是一個封閉的二元關係運算);而抽象函數也能推出一個等價關係。函數
equals()
應該比較抽象值是否相等。這和 equals()
比較行爲相等性是同樣的。hashCode()
應該將抽象值映射爲整數。equals()
和 hashCode()
.equals()
應該比較索引,就像 ==
同樣。一樣的,這也是比較行爲相等性。hashCode()
應該將索引映射爲整數。equals()
和 hashCode()
覆蓋,而是直接繼承 Object
中的方法。Java沒有爲大多數聚合類遵照這一規定,這也許會致使上面看到的隱祕bug。hashCode
必須爲兩個被該equals
方法視爲相等的對象產生相同的結果。首先來看Object中實現的缺省equals()
:性能
public class Object { ... public boolean equals(Object that) { return this == that; } }
在Object中實現的缺省equals()
是在判斷引用等價性。這一般不是程序員所指望的,所以須要重寫,下面是一個栗子:測試
public class Duration { ... // Problematic definition of equals() public boolean equals(Duration that) { return this.getLength() == that.getLength(); } }
嘗試以下客戶端代碼,可獲得this
Duration d1 = new Duration (1, 2); Duration d2 = new Duration (1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → false
基於以上結果進行如下解釋:spa
d2
和o2
最終參照相同的對象在內存中,對他們來講你仍然獲得不一樣的結果。 Duration
已經超載equals()
,由於方法簽名與Object’s 不相同。咱們實際上有兩種equals()
方法:隱式equals(Object)
繼承Object
,和新的equals(Duration)
。Object
參考,那麼d1.equals(o2)
咱們最終會調用equals(Object)
實現。Duration
參考,如在d1.equals(d2)
,咱們最終調用equals(Duration)
版本。o2
,d2
二者都會在運行時指向同一個對象!平等已經變得不一致。
咱們須要註釋 @Override ,重寫超類中的方法,所以,這裏實施正確的 equals() 方法:
code
@Override public boolean equals (Object thatObject) { if (!(thatObject instanceof Duration)) return false; Duration thatDuration = (Duration) thatObject; return this.getLength() == thatDuration.getLength(); }
再次執行客戶端代碼,可獲得:
Duration d1 = new Duration(1, 2); Duration d2 = new Duration(1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → true
回憶以前咱們對於相等的定義,即它們不能被使用者觀察出來不一樣。而對於可變對象來講,它們多了一種新的可能:經過在觀察前調用改造者,咱們能夠改變其內部的狀態,從而觀察出不一樣的結果。
List
對象包含相同的序列元素,那麼equals()
操做就會返回真。在有些時候,觀察等價性可能致使bug,甚至可能破壞RI。
假設咱們作了一個List
,而後把它放到Set
:
List<String> list = new ArrayList<>(); list.add("a"); Set<List<String>> set = new HashSet<List<String>>(); set.add(list);
咱們能夠檢查該集合是否包含咱們放入其中的列表,而且它會:
set.contains(list) → true
可是若是咱們修改這個存入的列表:
list.add("goodbye");
它彷佛就不在集合中了!
set.contains(list) → false!
事實上,更糟糕的是:當咱們(用迭代器)循環遍歷這個集合時,咱們依然會發現集合存在,可是contains()
仍是說它不存在!
for (List<String> l : set) { set.contains(l) → false! }
若是一個集合的迭代器和contains()
都互相沖突的時候,顯然這個集合已經被破壞了。
發生了什麼?咱們知道 List<String>
是一個可變對象,而在Java對可變對象的實現中,改造操做一般都會影響 equals()
和 hashCode()
的結果。因此列表第一次放入 HashSet
的時候,它是存儲在這時 hashCode()
對應的索引位置。可是後來列表發生了改變,計算 hashCode()
會獲得不同的結果,可是 HashSet
對此並不知道,因此咱們調用contains
時候就會找不到列表。
當 equals()
和 hashCode()
被改動影響的時候,咱們就破壞了哈希表利用對象做爲鍵的不變量。
下面是 java.util.Set
規格說明中的一段話:
注意:當可變對象做爲集合的元素時要特別當心。若是對象內容改變後會影響相等比較並且對象是集合的元素,那麼集合的行爲是不肯定的。
咱們應該從這個例子中吸收教訓,對可變類型,實現行爲等價性便可,也就是說,只有指 向一樣內存空間的objects,纔是相等的。因此對可變類型來講,無需重寫這兩個函數,直接繼承 Object對象的兩個方法便可。 若是必定要判斷兩個可變對象看起來是否一致,最好定義一個新的方法。