谈谈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

posted @ 2013-03-25 12:21  macemers  阅读(945)  评论(0编辑  收藏  举报