谨慎的覆盖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第十一条的摘要。

相关文章
相关标签/搜索