有些专家级程序员干脆历来不去覆盖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第十一条的摘要。