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