這是第2篇
二。對於所有對象都通用的方法
主要介紹如何正確地改寫Object類中的非final方法。
第7條:在改寫equals的時候請遵守通用的約定
改寫equals方法所必須遵守的幾個約定
1。自反性:X.equals(X)必須返回true
2。對稱性:Y.equals(X)返回的必須和X.equals(Y)一致
3。傳遞性:X.equals(Y),Y.equals(Z)如果返回true的話,那麼X.equals(Z)也必須返回true
4。一致性:多次調用X.equals(Y)返回的值應該是一致的
5。非空性:X.equals(null)必須返回false
這裡最容易產生錯誤的是第2和第3條。要保證第2條你必須保證不擴大比較的對象的范圍。而第3條產生的問題一般在繼承的過程中,子類擴展了父類,增加了新的變量,涉及到面向對象關系理論的一個基本問題,那就是:
要想在擴展一個可實例化的類的同時,既要增加新的特征,同時還要保留equals約定,沒有一個簡單的方法可以做到這一點。
撰寫高質量的equals方法的一些處方或者說告戒:
a.使用"=="檢查引用是否為空
b.使用instanceOf檢查對象的類別是否正確
c.把實參轉換到正確的類型,並保證實際參數中的"關鍵域"與當前對象中對應的域是否匹配
d.編寫完equals之後應該檢查是否滿足:對稱性,傳遞性,一致性
e.當你改寫equals的時候,總要改寫hashCode
f.不要企圖讓equals方法過於聰明,加入太多等價關系比較只會讓事情變糟糕
g.不要使equals方法依賴於不可靠的資源
h.不要使equals聲明中的Object對象替換為具體的類型對象
不過,你可以用組合代替繼承,在新的類中加入一個原有類的對象,而不是繼承它,這樣就不會遇到保持equals的問題了。
第8條:在改寫equals時總是要改寫hashCode
如果不這樣做的話,就導致該類無法與所有基於散列值的集合類型一起正常工作,如HashSet,HashMap。一個理想的散列函數應該把一個集合中的不相等的對象均勻的分布到所有可能的散列值上,書中提供了一種方法接近理想狀態
1.把某個非0的常整數保存在一個叫result的int型變量中
2。對於對象中的每一個關鍵域f完成下列步驟
a。為該域計算散列碼c:
i.如果是boolean f?0:1
ii.byte,char,shor,int型 (int)f
iii.long型 (int)(f^(f>>32))
ivfloat型 Float.floattoIntBits(f)
v.double型 Double.doubletoLongBits(f)得到一個long型,然後按iii計算
vi.如果是一個對象引用可以遞歸調用hashCode()或者計算一個另外的“規范表示”
vii。如果是一個數組,則把每一個元素當作一個單獨的域來處理
b.按公式計算散列碼 result=37*result+c;
3。返回result
還一點,一定要包括該類的關鍵性的變量,不要試圖通過排除一個對象的關鍵部分來獲得性能的提高
第9條:總是改寫toString()
這一條更多的是為了提供更有價值的信息給使用者,我很少使用這點,慚愧
第10條:謹慎地改寫clone()
實現對象的深層拷貝。可以這樣認為,clone()方法是另一個構造函數!要編寫一個行為正確的clone()方法,特別是對於一些類的內部具有可變的域是相當困難的。首先你必須實現cloneable接口,在clone()方法中調用super.clone(),然後修改任何需要修改的變量。例如,要對上一騙文章的Stack撰寫一個正確的clone方法如下
public Object clone() throws CloneNotSupportedException{
Stack result=(Stack)super.clone();
result.elements=(Object)elements.clone();
return result;
}
前提要求elements不是final.
clone()方法的改寫是復雜的,甚至是危險的,還有另一種方法來提供拷貝對象的功能,那就是拷貝構造函數。這同樣不是C++專有的概念。
例如public Example(Example),你傳入一個同類型的對象,然後在這個構造函數中做變量的拷貝就OK了。相比於Cloneable接口的復雜度,這是一個更好的解決辦法。
第11條:考慮實現Compareable接口
此接口的實現類似於equals方法,也要保持對稱,傳遞,一致等特性,具體不再介紹,如果你需要對某個類的對象實行排序等算法或者與Collections Framework中的類打交道,你該考慮實現這個接口