天天看點

高效Java之覆寫clone方法與實作Comparable的正确姿勢

作者:授道者

原則1:謹慎覆寫clone方法

一個類要是能克隆的就必須實作Cloneable接口,并且正确地覆寫clone方法.如何正确地覆寫clone方法呢?

1.如果父類沒有正确地覆寫clone方法,則影響了子類clone方法的正确行為;

2.覆寫clone方法時務必用super.clone()調用父類的clone方法,如果一個類不這麼做,而是通過調用構造器傳回克隆對象,那麼其子類可能得到一個錯誤的類執行個體(譬如由于類繼承關系導緻沒有正确的初始化),因為其違背了Object類中clone方法體系的定義.

3.clone方法類似構造器方法中的調用鍊關系,即子類構造器會(非顯式)調用父類構造器;

4.如果成員變量中有可變的引用類型,務必使用深拷貝,而非簡單地調用super.clone()來淺拷貝對象;

5.在clone方法中,與構造器方法一樣,不能調用可覆寫的方法,因為這樣會引起不确定的行為;

6.如果實作的是線程安全的類,那麼clone方法也要像其他方法一樣注意同步(synchronize);

7.接口或為繼承而設計的類最好不要實作Cloneable接口,因為實作了Cloneable接口意味着類中必須實作正确的、良好行為的clone方法,其子類也必須實作clone方法,别無選擇。如果沒有實作Cloneable接口,而想實作克隆對象,可用複制構造器或複制靜态工廠方法替代clone方法,這往往是最佳實踐。除了克隆數組對象适用于clone方法外,大神級程式員一般都不會實作Cloneable接口,而是用複制構造器或複制靜态工廠方法代替。

// Copy constructor
public Yum(Yum yum) { ... };
                     
// Copy factory
public static Yum newInstance(Yum yum) { ... };
                                        
// Clone method for class with references to mutable state
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}           

原則2:考慮實作Comparable接口

一個類實作了Comparable接口表明了該類的執行個體是可排序的。在集合架構中,可排序集合(TreeMap、TreeSet)都依賴于類實作的比較方法,是以如果類是可排序的,特别是值對象的類,要考慮實作Comparable接口或提供比較器。與實作equal方法的契約類似,實作比較方法也要滿足:

1.反身性/對稱性 -- sgn(x.compareTo(y)) == -sgn(y.compareTo(x));

2.傳遞性 -- (x.compareTo(y) > 0 && y.compareTo(z) > 0) 蘊含 x.compareTo(z) > 0;

3. x.compareTo(y) == 0,對于所有的z蘊含sgn(x.compareTo(z)) == sgn(y.compareTo(z));

4. 強烈推薦,非必須 -- (x.compareTo(y) == 0) == x.equals(y).如果違反了這條規則,最好說明描述.

基本類型的比較推薦用Double.compare/Long.compare方法.多個字段比較時,可以先比較重要的字段,若結果是0,立即傳回,否則繼續比較下一個字段,直到比較所有字段.

// Multiple-field Comparable with primitive fields
public int compareTo(PhoneNumber pn) {
int result = Short.compare(areaCode, pn.areaCode);
if (result == 0) {
result = Short.compare(prefix, pn.prefix);
if (result == 0)
result = Short.compare(lineNum, pn.lineNum);
}
return result;
}

//盡管有點性能損耗,java8 的實作方式更加簡潔
// Comparable with comparator construction methods
private static final Comparator<PhoneNumber> COMPARATOR =
comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}           

繼續閱讀