zoukankan      html  css  js  c++  java
  • 谈谈Java Clone

    Java里的Clone, 顾名思义就是克隆一个类的对象。克隆的方法分为浅拷贝(shallow copy)深拷贝(deep copy)。Clone的默认方法是shallow copy,考虑以下情况:

    1. 对象中所有数据域都属于基本类型,并无对其它对象的引用

    2. 原始对象和浅拷贝得到的克隆对象所引用的其它对象是不可变的,如String

    这两种情况下,使用默认的shallow copy并无任何问题,因为基本类型和不可变类型,均是不可变的。

    但更多的情况是,要克隆的对象中存在可变的其它对象,例如Date这个类:

    Date类属于可变类。要对这样的类进行Clone,必须重新定义clone()方法,以便使引用的对象也拷贝一份。

    通过默认的shallow copy,如上图所示,原始对象和克隆对象仍是引用同一个Date对象。

    而通过deep copy,原始对象和克隆对象才会引用不同的Date对象。换而言之,deep copy将对象里面引用的其它对象也copy了一份。

    实现deep copy的方法如下:

    1. 类实现Cloneable接口(tagging/marker interface,没有方法)

    2. 使用pulibc修饰符重写clone方法

    请看下面代码:

     1 public class Employee implements Cloneable {
     2 
     3     public String name;
     4     public int salary;
     5     public Calendar employedDate;
     6     
     7     public Employee(String name, int salary, Calendar employedDate) {
     8         super();
     9         this.name = name;
    10         this.salary = salary;
    11         this.employedDate = employedDate;
    12     }
    13     @Override
    14     public String toString() {
    15         return "Employee [name=" + name + ", salary=" + salary
    16                 + ", employedDate=" + employedDate.getTime() + "]";
    17     }
    18     
    19     //implements clone()
    20     public Employee clone() throws CloneNotSupportedException{
    21         Employee cloned = (Employee) super.clone();
    22         cloned.employedDate = (Calendar) employedDate.clone(); //clone this object
    23         
    24         return cloned;
    25     }
    26     
    27     /**
    28      * @param args
    29      * @throws CloneNotSupportedException 
    30      */
    31     public static void main(String[] args) throws CloneNotSupportedException {
    32         // TODO Auto-generated method stub
    33         Calendar now = Calendar.getInstance();
    34         Employee original = new Employee("Mike",5000,now);
    35         
    36         Employee copy = (Employee) original.clone();
    37         copy.name = "Kite";
    38         copy.salary = 8000;
    39         copy.employedDate.set(2010, 01, 01);
    40         
    41         System.out.println(original.toString());
    42         System.out.println(copy.toString());
    43     }
    44 
    45 }
    46 /*
    47  * output:
    48  * Employee [name=Mike, salary=5000, employedDate=Mon Mar 25 11:06:05 CST 2013]
    49    Employee [name=Kite, salary=8000, employedDate=Mon Feb 01 11:06:05 CST 2010]
    50 */

    从输出可见,拷贝的对象的Calendar引用已经和原始对象并不一样了,证明了deep copy成功。需要注意的是,如果在clone()方法中含有没有实现Cloneable接口的对象,就会抛出CloneNotSupportException异常。

    写了那么多,是不是说明clone很常用,自定义的类(如Employee类)都需要实现Cloneable接口呢?并非如此!恰恰相反,Core Java中明确提出:应该完全避免使用使用clone,并通过其它方法达到拷贝的目的。

    1. clone显得笨拙,需要重写clone方法并克隆每一个引用对象。

    2. clone在标准类库中并不普遍,只用不到5%的类实现了clone

    3. clone禁止final在类成员的使用(clone prevents the use of final fields.). 必须找其他方法去做。

    3. 最重要的一点,大部分接口和抽象类没有实现public clone这个方法

    拷贝对象是避免不了的,下面是其他可以代替clone的深拷贝的方法:

    1. 利用工厂模式 (Effective Java Item 11)

    2. 使用Java序列化机制,不过相当低效;也有据说高效的方法,暂时还没验证:http://javatechniques.com/blog/faster-deep-copies-of-java-objects/

    3. 专门的开源包:http://www.genericdeepcopy.com/ https://code.google.com/p/cloning/

    以下看看一些实际运用:

    1 List<String> b = new ArrayList<String>(a);

    创建一个a的shallow copy,并赋予b。而:

    1         List<String> a = new ArrayList<String>();
    2         a.add("a");
    3         a.add("b");
    4         a.add("c");
    5         
    6         List<String> b = new ArrayList<String>(a.size());
    7         System.out.println(b.size()); //output 0
    8         Collections.copy(b, a);

    Collections.copy()同样是shallow copy。但这里会抛出 java.lang.IndexOutOfBoundsException: Source does not fit in dest 这个异常。

    这是因为ArrayList(a.size())给List b初始化的是capacity,而不是size。所以b.size()输出是0。而Collections.copy()需要两者的size()一样。所以抛出异常。

    解决办法是如下:

    1 List<String> b = new ArrayList<String>(a);
    2         
    3 System.out.println(b.size()); //output 3
    4 Collections.copy(b, a);

    这样b.size()的结果为3,但事实上。由于第一行已经是浅拷贝了,这样的话第4行就显得多余。

    下面是Array(Collection c)的源码:

    1 public ArrayList(Collection<? extends E> c) {
    2          elementData = c.toArray();
    3          size = elementData.length;
    4         // c.toArray might (incorrectly) not return Object[] (see 6260652)
    5          if (elementData.getClass() != Object[].class)
    6              elementData = Arrays.copyOf(elementData, size, Object[].class);
    7      }

    参考

    Core Java 6.2

  • 相关阅读:
    Linux:打印(输出)所有的列(awk, $0)
    Linux:批量修改分隔符(awk、BEGIN、FS、OFS、print、tr命令)
    vcf格式文件转化为Excel(csv)格式文件(R语言的write.csv,write.table功能,Excel表的文件导入功能)
    Linux追加文件内容并在内容前加上该文件名(awk, FILENAME功能妙用)
    斯坦福大学公开课机器学习:监督学习在行人检测的应用(supervised learning for pedestrian detection)
    斯坦福大学公开课机器学习:梯度下降运算的学习率a(gradient descent in practice 2:learning rate alpha)
    斯坦福大学公开课机器学习:梯度下降运算的特征缩放(gradient descent in practice 1:feature scaling)
    解决invalid record found in VCF4 file (at least 8 tab-delimited fields expected)问题,批量修改空格改为制表格格式
    vcf文件(call variants得来的)怎么看变异是纯合还是杂合的
    剑指offer四十八之不用加减乘除做加法
  • 原文地址:https://www.cnblogs.com/techyc/p/2980505.html
Copyright © 2011-2022 走看看