1. 程式人生 > >對於所有物件都通用的方法

對於所有物件都通用的方法

8.覆蓋equals時請遵守通用規定

需要滿足的條件: 

類的每個例項本質上都是唯一的。 
不關心類是否提供了“邏輯相等(logical equality)”的測試功能。 
超類已經覆蓋了equals,從超類繼承過來的行為對於子類也是合適的。 
類是私有的或是包級私有的,可以確定他的equals方法永遠不會被呼叫。 


需要覆蓋equals:如果類具有自己特有的“邏輯相等”概念,而且超類還沒有覆蓋equals以實現期望的行為,這時我們就需要覆蓋equals方法。 

不需要覆蓋equals:用例項受控確保“每個值至多隻存在一個物件”的類。列舉型別就屬於這種類。對於這樣的類而言,邏輯相同與物件等同是一回事。 



equals方法實現等價關係通用約定(equivalence relation): 


自反性(reflexive):對於任何非null的引用值x,x.equals(x)必須返回true。 
如果違背,當你把該類的例項新增到集合(collection)中,然後該集合的contains會告訴你,沒有包含你剛剛新增的例項。 

對稱性(symmetric):對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。 
一個例項對比不區分了大小寫,但是,反過來對比是區分的,就導致了不對稱: 

傳遞性(transitive):對於任何非null的引用值x、y和z,如果x.equals(y)返回true,並且y.equals(z)也返回true,那麼x.equals(z)也必須返回true。 


子類增加的資訊會影響到equals的比較結果。 

不錯的權宜之計(workaround):使用複合。

一致性(consistent):對於任何非null的引用值x和y,只要equals的比較操作在物件中所用的資訊沒有被修改,多次呼叫x.equals(y)就會一致的返回true,或者一致的返回false。 
都不要是equals方法依賴於不可靠的資源。 

非空性(Non-nullity):對於任何非null的引用值x,x.equals(null)必須返回false。 


高質量equals方法: 

1、使用==操作符檢查“引數是否為這個物件的引用”。 
2、使用instanceof操作符檢查“引數是否為正確的型別”。 

3、把引數轉換成正確的型別。 
4、對於該類的每個“關鍵(significant)”域,檢查引數中的域是否與該物件中對應的域相匹配。 
這點myeclipse自動生成的並不完全完美,myeclipse生成如下: 
else if (!name.equals(other.name)) 
return false; 
但是,這條是要求你改成: 
else if (name != other.name && !name.equals(other.name)) 
return false; 
5、當你編寫完成了equals方法之後,應該會問自己三個問題:他是否的對稱的、傳遞的、一致的。 
(覆蓋equals時總要覆蓋hashcode;不要企圖讓equals方法過於智慧;不要將equals宣告中的Object物件替換為其他的型別。) 

9.覆蓋equals時總要覆蓋hashcode

每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法。 
如果不這樣的話,就會違反Object.hashCode的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常運作,這樣的集合包括HashMap、HashSet和Hashtable。

在引用程式的執行期間,只要物件的equals方法的比較操作所用到的資訊沒有被修改,那麼對這同一個物件呼叫多次,hashCode方法都必須始終如一的返回同一個整數。在一個應用程式的多次執行過程中,每次執行所返回的整數可以不一致。 
如果連個物件根絕equals方法比較是相等的,那麼呼叫這兩個物件中任意一個物件的hashCode方法都必須產生同樣的整數結果。 
如果兩個物件根據equals方法比較是不相等的,那麼呼叫這兩個物件中任意一個物件的hashCode方法,則不一定要蠶聲不同的整數結果。但是程式設計師應該知道,給不相等的物件產生截然不同的整數結果,有可能提高散列表(hash table)的效能。(比如,當你一個entity只根據id比較是否相等,但是在沒例項化之前,沒有id數值,那麼預設的equals返回false,但是hashCode返回的值卻相等。) 
語言之外的(extralinguistic)機制:無需呼叫構造器就可以建立物件。 

如果你覆蓋了非final類中的clone方法,則應該返回一個通過呼叫super.clone而得到的物件。 

實際上,對於實現了Cloneable的類,我們總是期望他也提供一個功能適當的公有的clone方法。 
通常情況下,除非該類的所有超類都提供了行為良好的clone實現,無論是共有的還是受保護的,否則,都不可能這麼做。 


如果你想在一個類中實現Cloneable,首先他的超類都需要提供行為良好的clone方法。一個好的雜湊函式通常傾向於“為不相等的物件產生不相等的雜湊碼”。 


必須排除equals比較計算中沒有用到的任何域,否則很有可能違反hashCode約定的第二條。 

不要試圖從雜湊碼計算中排除掉一個物件的關鍵部分來提高效能。  提供好的toString實現可以使類用起來更加舒適。 

在實際應用中,toString方法應該返回物件中包含的所有值的關注的資訊。 

在實現toString的時候,必須要做出一個很重要的決定:是否在文件中指定返回值的格式。 

無論你是否決定指定格式,都應該在文件中明確的表明你的意圖。 
如果你要指定格式,則應該嚴格的這樣去做。 

無論是否指定格式,都為toString返回值中包含的所有資訊,提供一種程式設計式的訪問途徑。 
Cloneable介面的目的是作為物件的一個mixin介面(mixin interface),表明這樣的物件允許克隆(clone)。 
遺憾的是,他並沒有成功的達到這個目的。其主要的缺陷在於,他缺少一個clone方法,Object的clone方法是受保護的。 
如果不借助於反射(reflection),就不能僅僅因為一個對戲那個實現了Cloneable,就可以呼叫clone。 
當被克隆的物件裡面包含的域引用了可變的物件時: 
(實際上,clone方法就是另外一個構造器;你必須確保他能不會傷害到原始的物件,並確保正確的建立被克隆物件中的約束條件-invariant) 
clone架構與引用可變物件的final域的正常用法是不相相容的。  還要考慮在拷貝的過程中執行緒安全的問題,把被拷貝的物件設定為不可外部修改或者實現執行緒安全。 

最好提供某些其他的途徑來代替物件拷貝,或者乾脆不提供這樣的功能。

另一個實現物件拷貝的好辦法是提供一個拷貝構造器或拷貝工廠。

12.考慮使用comparable介面

compareTo方法並沒有在Object中宣告,它是Comparable介面中唯一的方法。compareTo方法不但允許進行簡單的等同性比價,而且允許執行順序比較,它與Object的equals方法具有相似的特徵,它還是個泛型。

類實現了Comparable介面,就表明它的例項具有內在的排序關係

一旦類實現了Comparable介面,它就可以跟許多泛型演算法以及依賴於該介面的集合實現協作。比如按字母順序,按數值順序或者按年代順序,compareTo方法的通用約定於equals方法的相似:

將這個物件與指定的物件進行比較。當該物件小於、等於或大於指定物件的時候,分別返回一個負數、零或者正整數。如果由於指定物件的型別而無法與該物件進行比較,則丟擲ClassCastException異常。

在下面的說明中,符號sgn(表示式)表示數學中的signum函式,它根據表示式的值為負值、零和正值,分別返回-1、0和1。

  • 必須確保所有的x和y都滿足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。

  • 必須確保這個比較關係是可以傳遞的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示著x.compareTo(z) > 0。

  • 最後,必須確保x.compareTo(y) == 0 暗示著所有的z都滿足sgn(x.compareTo(z)) == sgn(y.compareTo(z))

  • 強烈建議(x.compareTo(y) == 0) == (x.equals(y)),這個絕非必要,一般來說,任何實現了Comparable介面的類,若違反了這個條件,都應該明確說明。