读书人

阴影clone与深度clone

发布时间: 2012-12-18 12:43:41 作者: rapoo

影子clone与深度clone

class CloneClass implements Cloneable{ public int aInt; public Object clone(){ CloneClass o = null; try{ o = (CloneClass)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } return o; }}

?

有三个值得注意的地方,一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。另一个值得请注意的是重写了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。

应该说第三点是最重要的,仔细观察一下Object类的clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重写clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重写之后要把clone()方法的属性设置为public。

那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。

以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。

?

package clone;class UnCloneA { private int i; public UnCloneA(int ii) { i = ii; } public void doubleValue() { i *= 2; } public String toString() { return Integer.toString(i); }}class CloneB implements Cloneable{ public int aInt; public UnCloneA unCA = new UnCloneA(111); public Object clone(){ CloneB o = null; try{ o = (CloneB)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } return o; }}public class CloneMain { public static void main(String[] a){ CloneB b1 = new CloneB(); b1.aInt = 11; System.out.println("before clone,b1.aInt = "+ b1.aInt); System.out.println("before clone,b1.unCA = "+ b1.unCA); CloneB b2 = (CloneB)b1.clone(); b2.aInt = 22; b2.unCA.doubleValue(); System.out.println("================================="); System.out.println("after clone,b1.aInt = "+ b1.aInt); System.out.println("after clone,b1.unCA = "+ b1.unCA); System.out.println("================================="); System.out.println("after clone,b2.aInt = "+ b2.aInt); System.out.println("after clone,b2.unCA = "+ b2.unCA); }}/** RUN RESULT:before clone,b1.aInt = 11before clone,b1.unCA = 111=================================after clone,b1.aInt = 11after clone,b1.unCA = 222=================================after clone,b2.aInt = 22after clone,b2.unCA = 222*/

?

输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。

?

package clone.ext;class UnCloneA implements Cloneable{ private int i; public UnCloneA(int ii) { i = ii; } public void doubleValue() { i *= 2; } public String toString() { return Integer.toString(i); } public Object clone(){ UnCloneA o = null; try{ o = (UnCloneA)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } return o; }}class CloneB implements Cloneable{ public int aInt; public UnCloneA unCA = new UnCloneA(111); public Object clone(){ CloneB o = null; try{ o = (CloneB)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } o.unCA = (UnCloneA)unCA.clone(); return o; }}public class CloneMain { public static void main(String[] a){ CloneB b1 = new CloneB(); b1.aInt = 11; System.out.println("before clone,b1.aInt = "+ b1.aInt); System.out.println("before clone,b1.unCA = "+ b1.unCA); CloneB b2 = (CloneB)b1.clone(); b2.aInt = 22; b2.unCA.doubleValue(); System.out.println("================================="); System.out.println("after clone,b1.aInt = "+ b1.aInt); System.out.println("after clone,b1.unCA = "+ b1.unCA); System.out.println("================================="); System.out.println("after clone,b2.aInt = "+ b2.aInt); System.out.println("after clone,b2.unCA = "+ b2.unCA); }}/** RUN RESULT:before clone,b1.aInt = 11before clone,b1.unCA = 111=================================after clone,b1.aInt = 11after clone,b1.unCA = 111=================================after clone,b2.aInt = 22after clone,b2.unCA = 222*/

?

可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。

要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重写clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

package clone;class CloneC implements Cloneable{ public String str; public StringBuffer strBuff; public Object clone(){ CloneC o = null; try{ o = (CloneC)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } return o; } }public class StrClone { public static void main(String[] a){ CloneC c1 = new CloneC(); c1.str = new String("initializeStr"); c1.strBuff = new StringBuffer("initializeStrBuff"); System.out.println("before clone,c1.str = "+ c1.str); System.out.println("before clone,c1.strBuff = "+ c1.strBuff); CloneC c2 = (CloneC)c1.clone(); c2.str = c2.str.substring(0,5); c2.strBuff = c2.strBuff.append(" change strBuff clone"); System.out.println("================================="); System.out.println("after clone,c1.str = "+ c1.str); System.out.println("after clone,c1.strBuff = "+ c1.strBuff); System.out.println("================================="); System.out.println("after clone,c2.str = "+ c2.str); System.out.println("after clone,c2.strBuff = "+ c2.strBuff); }}/* RUN RESULTbefore clone,c1.str = initializeStrbefore clone,c1.strBuff = initializeStrBuff=================================after clone,c1.str = initializeStrafter clone,c1.strBuff = initializeStrBuff change strBuff clone=================================after clone,c2.str = initiafter clone,c2.strBuff = initializeStrBuff change strBuff clone**/

?打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。

应该知道的是在Java中所有的基本数据类型都有一个相对应的类,象Integer类对应int类型,Double类对应double类型等等,这些类也与String类相同,都是不可以改变的类。也就是说,这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。