谨慎的覆盖clone方法

说在前面

有些专家级程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。

其他方式

可以提供一个构造函数或者工厂去实现clone功能。

相比于clone,它们有如下优势:

  1. 不依赖于某一种很有风险的、语言之外的对象创建机制;
  2. 不要求遵守尚未定制好的文档规范;
  3. 不会与final域发生冲突;
  4. 不会抛出不必要的受检异常;
  5. 不需要进行类型转换;

例如,通用集合的实现都提供了一个拷贝构造函数,它的参数类型为Collection或Map。

假如要把一个HashSet拷贝成一个TreeSet:

HashSet s = ...
new TreeSet(s)

如果一定要覆盖clone方法,那么则需要了解以下它的注意事项了。

Clone规范

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

行为良好的clone方法可以调用构造器来创建对象,构造之后再复制内部数据。

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被返回的对象则可能正是所需要的对象,在这种情况下不需要在做进一步的处理。

复杂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第十一条的摘要。

转载于:https://my.oschina.net/u/2450666/blog/2209179

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值