謹慎的覆蓋clone方法

說在前面

有些專家級程序員乾脆歷來不去覆蓋clone方法,也歷來不去調用它,除非拷貝數組。java

其餘方式

能夠提供一個構造函數或者工廠去實現clone功能。程序員

相比於clone,它們有以下優點:數組

  1. 不依賴於某一種頗有風險的、語言以外的對象建立機制;
  2. 不要求遵照還沒有定製好的文檔規範;
  3. 不會與final域發生衝突;
  4. 不會拋出沒必要要的受檢異常;
  5. 不須要進行類型轉換;

例如,通用集合的實現都提供了一個拷貝構造函數,它的參數類型爲Collection或Map。ide

假如要把一個HashSet拷貝成一個TreeSet:函數

HashSet s = ...
new TreeSet(s)

若是必定要覆蓋clone方法,那麼則須要瞭解如下它的注意事項了。this

Clone規範

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

行爲良好的clone方法能夠調用構造器來建立對象,構造以後再複製內部數據。spa

Clone作法

  1. 全部實現了Cloneable接口的類都應該用一個公有方法覆蓋clone;
  2. 此公有方法首先要調用super.clone,而後在修正須要修正的域;
// 僞代碼
class User implements Cloneable {

    @Override
    public User clone() {
        User user = (User)super.clone(); // 1.先調用super.clone
        user.set ...               // 2.在修正
    }

}

Clone要點

若是覆蓋了非final類中的clone方法,則應該返回一個經過調用super.clone而獲得的對象,若是類的全部父類都遵照這條規則,那麼調用super.clone最終會調用Object的clone方法,從而建立出正確類的實例。這種機制大致上相似於自動的構造器調用鏈。設計

簡單Clone

若是類中包含的每一個域是一個基本類型的值,或者包含的是一個指向不可變對象的引用,那麼調用clone被返回的對象則可能正是所須要的對象,在這種狀況下不須要在作進一步的處理。code

複雜Clone

若是類中包含的域是指向一個可變對象的引用,那麼就要當心的對其進行clone。對象

例如,若類中存在一個Object[]數組,則能夠參考一下作法:

// 僞代碼
class Stack {
    private Object[] elements;
    private int size = 0;

    @Override
    public Stack clone() {
        Stack result = (Stack) super.clone();
        result.elements = this.elements.clone();
    }
}

還有一種狀況,若類中存在一個對象或者集合(自定義對象、List、Map等),那麼光調用這些對象的clone還不夠,例如編寫一個散列表的clone方法,它的內部數據包含一個散列桶數組:

// 僞代碼
class HashTable implements Cloneable {
    private Entry[] buckets = ...

    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(key, value, next) ...
    }
}

若是隻調用了buckets.clone,其實克隆出來的buckets和被克隆的buckets內的entry是引用着同一對象的。

這種狀況下,必須單獨拷貝並組成每一個桶的鏈表,例如:

// 僞代碼
class HashTable implements Cloneable {
    private Entry[] buckets = ...

    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(key, value, next) ...
    }

    // 提供一個深拷貝函數
    Entry deepCopy() {
        return new Entry(key, value, next == null ? null : next.deepCopy());
    }

    @Override
    public HashTable clone() {
        try ...
        HashTable result = (HashTable) super.clone();
        result.buckets = new Enrty[buckets.length];
        for(int i=0;i<buckets.length;i++) {
            if(buckets[i] != null) 
                result.buckets[i] = buckets[i].deepCopy();
        }
        return result;
        catch CloneNotSupportedException e ...
    }

}

提供一個深拷貝方法,遍歷源對象的buckets,將它拷貝到新對象中。

這種作法有一個肯定,若是散列桶很長,很容易致使棧溢出,由於遞歸的層級太多!

解決這種問題,能夠採用迭代(iteration)來代替遞歸(recursion),修改一下deepCopy方法:

Entry deepCopy() {
  Entry result = new Entry(key, value, next);
  for (Entry p = result; p.next != null; p = p.next) {
    p.next = new Entry(p.next.key, p.next.value, p.next.next);
  }
  return result;
}

最好還作到

Object的clone方法被聲明爲可跑出CloneNotSupportedException異常,可是,覆蓋版本的clone方法可能會忽略這個聲明。公有的clone方法應該省略這個聲明,由於不會跑出受檢異常的方法用起來更輕鬆。

若是專門爲了繼承而設計的類覆蓋類clone方法,覆蓋版本的clone方法就應該模擬Object.clone的行爲:

  1. 聲明爲protected;
  2. 拋出CloneNotSupportedException;
  3. 不實現CloneableJiekou ;

總結

以上就是對Effective Java第十一條的摘要。

相關文章
相關標籤/搜索