在很多場景下,需要判定兩個對象是否“相等”,例如,判斷某個Collection中是否包含特定元素。
“==”和“equals()”有何差別?如何定義ADT正确實作equals()
一. 什麼是等價性(Equality)
ADT是對資料的抽象,展現為一組對資料的操作。而抽象函數AF是将内部表示R轉換為抽象表示A。而等價性就是基于AF來定義的。
現實中每個對象都是獨特的,是以無法有完全相等,但有“相似性”,但是在數學中,“絕對相等”是存在的
二. 三個判斷等價性的方法
1. 使用AF來判定是否等價——如果AF映射到同樣的結果,那麼二者等價
2. 等價關系(滿足自反、傳遞、對稱)
-這二者實際上是相同的
· 等價關系引出了一個抽象函數
· 抽象函數所引起的關系是等價關系
3. 用戶端所觀察到的結果是相同的,則二者等價。
e.g. 考慮兩個集合{1, 2}和 {2, 1}。這二者在用戶端來看顯然是等價的。但是其内部的表示可能不完全相同
e.g.
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXycmeONzZq1EeRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zMzgTO1kTN5EzNwQDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
如果根據AF來判斷,隻有d1和d4是等價的,但是如果站在用戶端角度來看,調用getLength這個觀察器,d1,d3,d4的結果都是相同的,是以三者也是相同的。
三. ‘==’ vs. equals()
==:在Java之中這是用來判斷引用等價性(referential equality)
equals():在Java之中這是用來判斷對象等價性(object equality)
而對于不同的ADT時,判斷相等的條件也不盡相同,是以需要重寫equals()
對于基本資料類型,一般使用==來判斷相等;
但是對于對象資料類型,使用equals()來判斷相等。
· 如果使用==,是在判斷兩個對象的身份辨別ID是否相同(指向記憶體之中同一段空間)
· 最好使用equals()來判斷
在對象引用之中使用==是一種糟糕的選擇
e.g.
四. 不可變類型的等價性
在Object之中預設的equals()是在判斷引用等價性。但是這通常不是程式員所期望的,是以需要重寫。
e.g.
我們要判斷d1和d2、o2的等價關系,要針對Duration類之中的equals來判斷。很顯然,Duration之中有兩個equals(),一個的參數是Duration類,另一個是Object類,這是因為Duration是Object的子類,它繼承了Object類之中的equals(),而Duration中的equals()是對Object類中的equals()的重載。
是以在d1調用equals來判斷時,會在運作時發生動态分派。d1調用的equals()傳遞的參數類型是Object則會調用下面的equals(),如果是Duration類的則會調用上面的equals()。是以結果是
instanceOf()——這是用來判斷一個對象是不是某一個類型的操作。對于instanceOf的操作是一個動态類型檢查,而不是靜态類型檢查。通常情況下,在OOP之中使用instanceOf是一個糟糕的選擇,除了實作equals之外,它在任何地方都應該被禁止。這種對于探測對象運作時的類型的操作的禁止也包括getClass()
五. Object的“合同”
equals():當重寫equals()時,必須遵守它的一般規則
· equals必須是一個等價關系——它滿足自反的,對稱的,傳遞的
· equals應當是一緻的(consistent)——對于equals的多次調用的結果應當是一樣的
· 對于空指針(null)的引用所調用的equals必須傳回false
· 如果兩個對象相同,那麼它們的hashCode也必須相同
- hashCode确定了不同對象在哈希表之中的位置,而為了提高哈希表的效率,哈希表之中的元素最好均勻分布,即不同對象hashCode最好不相同,但是相同的對象,它們的hashCode必須相同,不然在哈希表之中的位置就不是同一個位置。
e.g.
- 是以在重寫equals的時候一定要重寫hashCode
六. 可變類型的等價性
觀察等價性(observational equality):在不改變對象狀态的情況下,兩個mutable對象看起來是否一緻
行為等價性(behaviorial equality):調用對象的任何方法都展現出一緻的結果
注意:不可變類型之中觀察等價性和行為等價性是相同的,因為不可變類型沒有變值器(Mutator)
對于可變類型來說,往往傾向于實作嚴格的觀察等價性
但在有些時候,觀察等價性可能導緻bug,甚至可能破壞RI
e.g.
然後我們判斷set之中是否含有list
接下來我們在list之中添加一個新的元素
然後在判斷set之中是否含有list
原因是:在list調用了add這個Mutator之後,它的hashCode發生了變化,但是存放它的哈希表并沒有意識到要将這個list放到一個新的地方,是以就無法再次根據哈希表來查找這個list。
用一個更形象的例子來說明:你在派出所申領了身份證,留了當時的照片;幾個月以後,你“整容”了(mutated),你用乘機的時候就無法比對到你的身份證照片了。
在Java之中Collections是使用觀察等價性,但是其它可變類(像是StringBuilder)是使用行為等價性。
· 對可變類型,實作行為等價性即可;也就是說隻有指向相同的記憶體空間才是相等的。
· 是以對可變類型來說,無需重寫這兩個函數,直接繼承Object的方法即可
· 如果一定要判斷兩個可變類型是否相等,最好定義一個新的方法
七. 自動封裝(Autoboxing)和等價性
基本資料類型和他們的對象資料類型是等價的 e.g. int 和 Integer
但是x==y -> false;因為==是引用等價性
但是對于(int)x == (int) y -> true
e.g.
顯然最後的判斷是false,因為a.get()所傳回的類型是Integer,而==是比較引用等價性,是以是false。
資料來源 MIT6.031 哈工大軟體構造課程