天天看點

Software Construction學習——ADT和OOP的相等性

在很多場景下,需要判定兩個對象是否“相等”,例如,判斷某個Collection中是否包含特定元素。

“==”和“equals()”有何差別?如何定義ADT正确實作equals()

一.    什麼是等價性(Equality)

ADT是對資料的抽象,展現為一組對資料的操作。而抽象函數AF是将内部表示R轉換為抽象表示A。而等價性就是基于AF來定義的。

現實中每個對象都是獨特的,是以無法有完全相等,但有“相似性”,但是在數學中,“絕對相等”是存在的

二.    三個判斷等價性的方法

1.    使用AF來判定是否等價——如果AF映射到同樣的結果,那麼二者等價

2.    等價關系(滿足自反、傳遞、對稱)

    -這二者實際上是相同的

        ·    等價關系引出了一個抽象函數

        ·    抽象函數所引起的關系是等價關系

3.    用戶端所觀察到的結果是相同的,則二者等價。

    e.g.    考慮兩個集合{1, 2}和 {2, 1}。這二者在用戶端來看顯然是等價的。但是其内部的表示可能不完全相同

e.g.

Software Construction學習——ADT和OOP的相等性
Software Construction學習——ADT和OOP的相等性

如果根據AF來判斷,隻有d1和d4是等價的,但是如果站在用戶端角度來看,調用getLength這個觀察器,d1,d3,d4的結果都是相同的,是以三者也是相同的。

三.    ‘==’    vs.    equals()

==:在Java之中這是用來判斷引用等價性(referential equality)

equals():在Java之中這是用來判斷對象等價性(object equality)

而對于不同的ADT時,判斷相等的條件也不盡相同,是以需要重寫equals()

Software Construction學習——ADT和OOP的相等性

對于基本資料類型,一般使用==來判斷相等;

但是對于對象資料類型,使用equals()來判斷相等。

    ·    如果使用==,是在判斷兩個對象的身份辨別ID是否相同(指向記憶體之中同一段空間)

    ·    最好使用equals()來判斷

在對象引用之中使用==是一種糟糕的選擇

e.g.    

Software Construction學習——ADT和OOP的相等性

四.    不可變類型的等價性

在Object之中預設的equals()是在判斷引用等價性。但是這通常不是程式員所期望的,是以需要重寫。

e.g.

Software Construction學習——ADT和OOP的相等性
Software Construction學習——ADT和OOP的相等性

我們要判斷d1和d2、o2的等價關系,要針對Duration類之中的equals來判斷。很顯然,Duration之中有兩個equals(),一個的參數是Duration類,另一個是Object類,這是因為Duration是Object的子類,它繼承了Object類之中的equals(),而Duration中的equals()是對Object類中的equals()的重載。

Software Construction學習——ADT和OOP的相等性

是以在d1調用equals來判斷時,會在運作時發生動态分派。d1調用的equals()傳遞的參數類型是Object則會調用下面的equals(),如果是Duration類的則會調用上面的equals()。是以結果是

Software Construction學習——ADT和OOP的相等性

instanceOf()——這是用來判斷一個對象是不是某一個類型的操作。對于instanceOf的操作是一個動态類型檢查,而不是靜态類型檢查。通常情況下,在OOP之中使用instanceOf是一個糟糕的選擇,除了實作equals之外,它在任何地方都應該被禁止。這種對于探測對象運作時的類型的操作的禁止也包括getClass()

五.    Object的“合同”

equals():當重寫equals()時,必須遵守它的一般規則

·    equals必須是一個等價關系——它滿足自反的,對稱的,傳遞的

·    equals應當是一緻的(consistent)——對于equals的多次調用的結果應當是一樣的

·    對于空指針(null)的引用所調用的equals必須傳回false

·    如果兩個對象相同,那麼它們的hashCode也必須相同

     -    hashCode确定了不同對象在哈希表之中的位置,而為了提高哈希表的效率,哈希表之中的元素最好均勻分布,即不同對象hashCode最好不相同,但是相同的對象,它們的hashCode必須相同,不然在哈希表之中的位置就不是同一個位置。

e.g.    

Software Construction學習——ADT和OOP的相等性

    -    是以在重寫equals的時候一定要重寫hashCode

六.    可變類型的等價性

觀察等價性(observational equality):在不改變對象狀态的情況下,兩個mutable對象看起來是否一緻

行為等價性(behaviorial equality):調用對象的任何方法都展現出一緻的結果

注意:不可變類型之中觀察等價性和行為等價性是相同的,因為不可變類型沒有變值器(Mutator)

對于可變類型來說,往往傾向于實作嚴格的觀察等價性

但在有些時候,觀察等價性可能導緻bug,甚至可能破壞RI

e.g.

Software Construction學習——ADT和OOP的相等性

然後我們判斷set之中是否含有list

Software Construction學習——ADT和OOP的相等性

接下來我們在list之中添加一個新的元素

Software Construction學習——ADT和OOP的相等性

然後在判斷set之中是否含有list

Software Construction學習——ADT和OOP的相等性

原因是:在list調用了add這個Mutator之後,它的hashCode發生了變化,但是存放它的哈希表并沒有意識到要将這個list放到一個新的地方,是以就無法再次根據哈希表來查找這個list。

用一個更形象的例子來說明:你在派出所申領了身份證,留了當時的照片;幾個月以後,你“整容”了(mutated),你用乘機的時候就無法比對到你的身份證照片了。

在Java之中Collections是使用觀察等價性,但是其它可變類(像是StringBuilder)是使用行為等價性。

·    對可變類型,實作行為等價性即可;也就是說隻有指向相同的記憶體空間才是相等的。

·    是以對可變類型來說,無需重寫這兩個函數,直接繼承Object的方法即可

·    如果一定要判斷兩個可變類型是否相等,最好定義一個新的方法

七.    自動封裝(Autoboxing)和等價性

基本資料類型和他們的對象資料類型是等價的    e.g.    int 和 Integer

Software Construction學習——ADT和OOP的相等性

但是x==y -> false;因為==是引用等價性

但是對于(int)x == (int) y -> true

e.g.

Software Construction學習——ADT和OOP的相等性

顯然最後的判斷是false,因為a.get()所傳回的類型是Integer,而==是比較引用等價性,是以是false。

資料來源    MIT6.031    哈工大軟體構造課程

繼續閱讀