一、概念
java中没有指针的概念,而实质上每个new语句返回的都是一个指针的作用。
java在对基本类型,例如int,char,double都是采用值传递,即传递一个输入参数的复制,除此之外的其他类型都是引用传递,“=”复制也是这样。
但是存在这种情况:
有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。此时如果用”=“复制,传递的就是引用,会导致指向同一块对象,而不是两个独立对象。
要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
Object类没有copy()函数
二、概念的示例
- package com.zoer.src;
- public class ObjRef {
- Obj aObj = new Obj();
- int aInt = 11;
- public void changeObj(Obj inObj) {
- inObj.str = "changed value";
- }
- public void changePri(int inInt) {
- inInt = 22;
- }
- public static void main(String[] args) {
- ObjRef oRef = new ObjRef();
- System.out.println("Before call changeObj() method: " + oRef.aObj); //(1)输出 init value
- oRef.changeObj(oRef.aObj);
- System.out.println("After call changeObj() method: " + oRef.aObj); //(2)输出 changevalue
- System.out.println("==================Print Primtive=================");
- System.out.println("Before call changePri() method: " + oRef.aInt); //(3)输出11
- oRef.changePri(oRef.aInt);
- System.out.println("After call changePri() method: " + oRef.aInt); //(4)输出11
- }
- }
- package com.zoer.src;
- public class Obj {
- String str = "init value";
- public String toString() {
- return str;
- }
- }
这段代码的主要部分调用了两个很相近的方法,changeObj()和changePri()。唯一不同的是它们一个把对象作为输入参数,另一个把Java中的基本类型int作为输入参数。并且在这两个函数体内部都对输入的参数进行了改动。看似一样的方法,程序输出的结果却不太一样。changeObj()方法真正的把输入的参数改变了,而changePri()方法对输入的参数没有任何的改变。
(3)输出11
(4)输出11
和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的函数体内部的操作都是针对这个拷贝的操作,函数执行结束后,这个局部变量也就完成了它的使命,它影响不到作为输入参数的变量。这种方式的参数传递被称为"值传递"。
(1)输出 init value
(2)输出 changevalue
而在Java中用对象作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递"。就是类似于给变量再起一个别名。两个名字都指向内存中的同一个对象。
三、使用
在一个普通的类中,clone()方法是不可以被外部类调用的,因为在java.lang.Object类中,clone()方法是一个protected类型的方法,如果要在自己的类中提供clone()方法给外部调用,就必须重写clone()方法,并将其访问限制修改为public,这样,在外部类中才可以调用该方法。但是修改为public后,在外部调用该方法时会报一个CloneNotSupportedException的异常,原来除了重写该方法外,新的类还需要实现java.lang.Cloneable接口,这是一个空接口,不用实现任何方法。
- public class TestClone implements Cloneable{
- @Override
- public Object clone() throws CloneNotSupportedException {
- // TODO Auto-generated method stub
- return super.clone();
- }
- }
克隆满足的条件:
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的有关。一般而言,clone方法满足以下条件:
1).对任何的对象x,都有:x.clone()!=x.即克隆对象与原对象不是同一个对象。
2).对任何的对象x,都有:x.clone().getClass==x.getClass(),即克隆对象与原对象的类型一样。
3).如果对象x的equals()方法定义恰当的话,x.clone().equals(x)应当是成立的。
四、影子克隆
- package com.zoer.src;
- 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 ObjRef {
- public static void main(String[] a) {
- CloneB b1 = new CloneB();
- b1.aInt = 11;
- System.out.println("before clone,b1.aInt = " + b1.aInt); //before clone,b1.aInt = 11
- System.out.println("before clone,b1.unCA = " + b1.unCA); //before clone,b1.unCA = 111
- CloneB b2 = (CloneB) b1.clone();
- b2.aInt = 22;
- b2.unCA.doublevalue();
- System.out.println("=================================");
- System.out.println("after clone,b1.aInt = " + b1.aInt); //before clone,b1.aInt = 11
- System.out.println("after clone,b1.unCA = " + b1.unCA); //(1)?怎么clone了还改变原值before clone,b1.unCA = 111
- System.out.println("=================================");
- System.out.println("after clone,b2.aInt = " + b2.aInt); //(2)因为clone了独立分体,after clone,b2.aInt = 22
- System.out.println("after clone,b2.unCA = " + b2.unCA); //(3)因为clone了独立分体,clone,b2.unCA = 222
- }
- }
输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
//before clone,b1.aInt = 11
//(2)因为clone了独立分体,after clone,b2.aInt = 22
int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。
//(1)?怎么clone了还改变原值before clone,b1.unCA = 111
//(3)因为clone了独立分体,clone,b2.unCA = 222
相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。
五、深度克隆
把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();
- package com.zoer.src;
- 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);
- }
- }
输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after 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,虽然这只是一个假象,但却大大方便了我们的编程。
StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:
- 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 RESULT
- before clone,c1.str = initializeStr
- before clone,c1.strBuff = initializeStrBuff
- =================================
- after clone,c1.str = initializeStr
- after clone,c1.strBuff = initializeStrBuff change strBuff clone
- =================================
- after clone,c2.str = initi
- after 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类中的函数都不能更改自身的值。