1. 程式人生 > 其它 >【Effective Java 13】謹慎地覆蓋 clone

【Effective Java 13】謹慎地覆蓋 clone

1. Object 中對 clone 介面的規範

clone 方法的通用約定是非常弱的,下面是來自 Object 規範中的約定內容:

建立和返回該物件的一個拷貝。這個 “拷貝” 的精確含義取決於該物件的類。一般的含義是,對於任何物件 x,有:

x.clone() != x; // true
x.clone.getClass() == x.getClass(); // true
x.clone().equals(x); // true

但這些要求不是絕對的。

2. clone 介面的規範

  • 永遠不要給不可變的類永遠都不應該提供 clone 方法。

  • clone 方法就是另一個構造器,必須確保它不會傷害到原始的物件,並確保正確地建立被克隆物件中的約定條件。比如當類中有陣列時,要注意拷貝陣列,而不是拷貝引用。

public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone(); // 拷貝陣列, 但 elements 必須是可變的
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
  • 就像序列化一樣,Cloneable 架構與引用可變物件的 final 域的正常用法是不相容的,除非在原始物件和克隆物件之間以安全地共享此可變物件。為了使類成為可克隆的,可能有必要從某些域中去掉 final 修飾符。

3. 更好的 clone 方法

物件拷貝的更好的方法是提供一個拷貝構造器(copy constructor)或拷貝工廠(copy factory)。拷貝構造器只是一個構造器,它唯一的引數型別是包含該構造器的類,而拷貝工廠是類似於拷貝構造器的靜態工廠:

// 拷貝構造器
public Yum(Yum yum) { ... }
// 拷貝工廠方法
public static Yum newInstance(Yum yum) { ... }

拷貝構造器的做法,及其靜態工廠方法的變形,都比 Cloneable/clone 方法具有更多優勢:它們不依賴於某一種很有風險的、語言之外的物件建立機制;它們不要求遵守尚未制定好的文件規範;它們不會與 final 域的正常使用發生衝突

;它們不會丟擲不必要的受檢異常;它們不需要進行型別轉換。

甚至,拷貝構造器或者拷貝工廠可以帶一個引數,引數型別是該類實現的介面。例如,按照慣例所有通用集合實現都提供了一個拷貝構造器,其引數型別為 Collection 或者 Map 介面。基於介面的拷貝構造器和拷貝工廠(更準確的叫法應該是轉換構造器轉換工廠),允許客戶選擇拷貝的實現型別,而不是強迫客戶接受原始的實現型別。例如,假設你有一個 HashSet:s,並希望把他拷貝成一個 TreeSet。clone 方法無法提供這樣的功能,但是轉換構造器很容易實現:new TreeSet<>(s)

4. 總結

複製最好由構造器或者工廠提供,陣列除外。對於陣列物件,還是使用 clone 方法,或 Arrays.copyOf()