博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谨慎的覆盖clone方法
阅读量:6840 次
发布时间:2019-06-26

本文共 2814 字,大约阅读时间需要 9 分钟。

hot3.png

说在前面

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

其他方式

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

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

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

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

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

HashSet s = ...new TreeSet(s)

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

Clone规范

x.clone() != x //truex.clone().getClass() == x.getClass() //truex.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,将它拷贝到新对象中。

这种做法有一个确定,如果散列桶很长,很容易导致栈溢出,因为递归的层级太多!

解决这种问题,可以采用迭代(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

你可能感兴趣的文章
java第七天(this关键字、构造器、static静态关键字、单例模式)
查看>>
Apache静态编译和动态编译详解
查看>>
spring boot之统一异常处理
查看>>
电邮靠谱指南
查看>>
Ural 1014-The Product of Digits
查看>>
Ubuntu安装JDK
查看>>
升级到 Ubuntu 12.04(LTS)
查看>>
C++基础语法总结
查看>>
ubuntu配置ftp服务器
查看>>
IT人士 不能一辈子靠技术生存
查看>>
我的友情链接
查看>>
Tomcat中server.xml参数说明
查看>>
SCCM2007 R2的部署前准备,SCCM系列之一
查看>>
利用mysql的binlog恢复数据
查看>>
yum upgrade 时出现 database disk image is malformed 错误的解决方案
查看>>
关于在使用iframe之后子页面中如何在父级弹窗的问题的具体实现
查看>>
Project Server 2013新手入门 (十二)特定工作组
查看>>
内容更新了《网络规划设计师考试考点分析与真题详解》(2013年8月印刷版)...
查看>>
弹性盒子
查看>>
十大经典排序算法的算法描述和代码实现
查看>>