有些專家級程序員乾脆歷來不去覆蓋clone方法,也歷來不去調用它,除非拷貝數組。java
能夠提供一個構造函數或者工廠去實現clone功能。程序員
相比於clone,它們有以下優點:數組
例如,通用集合的實現都提供了一個拷貝構造函數,它的參數類型爲Collection或Map。ide
假如要把一個HashSet拷貝成一個TreeSet:函數
HashSet s = ... new TreeSet(s)
若是必定要覆蓋clone方法,那麼則須要瞭解如下它的注意事項了。this
x.clone() != x //true x.clone().getClass() == x.getClass() //true x.clone.equals(x) // true
行爲良好的clone方法能夠調用構造器來建立對象,構造以後再複製內部數據。spa
// 僞代碼 class User implements Cloneable { @Override public User clone() { User user = (User)super.clone(); // 1.先調用super.clone user.set ... // 2.在修正 } }
若是覆蓋了非final類中的clone方法,則應該返回一個經過調用super.clone而獲得的對象,若是類的全部父類都遵照這條規則,那麼調用super.clone最終會調用Object的clone方法,從而建立出正確類的實例。這種機制大致上相似於自動的構造器調用鏈。設計
若是類中包含的每一個域是一個基本類型的值,或者包含的是一個指向不可變對象的引用,那麼調用clone被返回的對象則可能正是所須要的對象,在這種狀況下不須要在作進一步的處理。code
若是類中包含的域是指向一個可變對象的引用,那麼就要當心的對其進行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的行爲:
以上就是對Effective Java第十一條的摘要。