测试目标

1、  什么是浅层复制

2、  什么是深层复制

浅层复制

1、  创建一个TestObj对象,然后给此对象赋值。

2、  再生成一个克隆对象,比较此克隆对象与被克隆的对象各属性值。

新建测试入口类TestCloneable.java

public class TestCloneable{

private static TestCloneable testCloneable = new TestCloneable();

private void testClone(){

//原始对象

TestObj testObj = new TestObj();

//赋值

testObj.setTestObj2(1);

TestObj2 testObj2 = testObj.getTestObj2();

///

testObj.setName("tom");

String[] nameArr = new String[2];

nameArr[0] = "a";

nameArr[1] = "b";

testObj.setNameArr(nameArr);



System.out.println(testObj.getName()+">>"+testObj.getNameArr()+">>"+testObj2.display());

for (int i=0;i<testObj.getNameArr().length;i++){

System.out.println(testObj.getNameArr()[i]);

}

//克隆出来的对象

TestObj cloneTestObj1 = testObj.clone();

///

String[] nameArr2 = new String[2];

nameArr2[0] = "c";

nameArr2[1] = "d";

cloneTestObj1.setNameArr(nameArr2);

cloneTestObj1.setName("jim");

cloneTestObj1.getTestObj2().doAdd();

System.out.println(testObj.getName()+">>"+testObj.getNameArr()+">>"+testObj2.display());

for (int i=0;i<testObj.getNameArr().length;i++){

System.out.println(testObj.getNameArr()[i]);

}

System.out.println(cloneTestObj1.getName()+">>"+cloneTestObj1.getNameArr()+">>"+cloneTestObj1.getTestObj2().display());

for (int i=0;i<cloneTestObj1.getNameArr().length;i++){

System.out.println(cloneTestObj1.getNameArr()[i]);

}

}

/**

* @param args

*/

public static void main(String[] args) {

testCloneable.testClone();

}

}

运行main函数,输出结果为:

tom>>[Ljava.lang.String;@1fb8ee3>>1

a

b

tom>>[Ljava.lang.String;@1fb8ee3>>2

a

b

jim>>[Ljava.lang.String;@61de33>>2

c

d

以上输出中,前三行是原始对象的输出,中间三行是克隆对象cloneTestObj1设置属性值后,原始对象的输出,后三行是克隆对象的输出。

从上面中间三行可以看出,原始对象中的字符串和数组没有受到克隆对象设置值的影响,而类变量则受到了克隆对象的影响,也就是

cloneTestObj1.getTestObj2().doAdd();

改变的不仅仅是克隆对象还有原始对象,也就是克隆对象中类变量和原始对象指向的是同一个句柄,这种情况下的克隆对象往往不是我们所需要的,因为我们很显然需要一个完全与原始对象无关的对象,要做到这一点,就得用到深层复制了,也就是以上的克隆其实就是所说的浅层复制。

深层复制

在上面的TestObj类中,更改如下方法中的一行,将

clone.testObj2 = testObj2.clone();

注释去掉

public TestObj clone() {

TestObj clone = null;

try {

clone = (TestObj)super.clone();

//对对象的拷贝,如果没有这一行则是浅层复制

clone.testObj2 = testObj2.clone();

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

return clone;

}

再次运行TestCloneable的main方法,得到如下结果:

tom>>[Ljava.lang.String;@1fb8ee3>>1

a

b

tom>>[Ljava.lang.String;@1fb8ee3>>1

a

b

jim>>[Ljava.lang.String;@61de33>>2

c

d

从上面的输出结果中可以看到,原始对象testObj的属性值没有受到克隆对象cloneTestObj1的影响,这个就是所说的深层复制了。

说明

1、  在上面的类TestObj和TestObj2中,都实现了Cloneable接口,不然会抛出CloneNotSupportedException异常。

2、  在TestObj中,注意到只有类变量才需要额外的

clone.testObj2 = testObj2.clone();

处理,而字符串变量等不需要,这说明了在浅层克隆时对类类型的克隆只是克隆了原始对象的引用传递(reference)地址,并非是克隆了一个全新的对象。

3、  对象克隆在lucene中很多地方都用到了,比如在IndexInput.java中,所以在阅读lucene源代码的时候,弄清楚克隆的用法也是很有必要的。

Java的clone()方法


⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:


①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象


②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样


③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。




⑵Java中对象的克隆


①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。


②在派生类中覆盖基类的clone()方法,并声明为public。


③在派生类的clone()方法中,调用super.clone()。


④在派生类中实现Cloneable接口。




请看如下代码:




class Student implements Cloneable
 
{
 
    String name;
 
    int age;
 
    Student(String name,int age)
 
    {
 
        this.name=name;
 
        this.age=age;
 
    }
 
    public Object clone()
 
    {
 
        Object o=null;
 
        try
 
        {
 
        o=(Student)super.clone();//Object中的clone()识别出你要复制的是哪一
 
// 个对象。
 
        }
 
        catch(CloneNotSupportedException e)
 
        {
 
            System.out.println(e.toString());
 
        }
 
        return o;
 
    }
 
}
 

 
public static void main(String[] args)
 
    {
 
      Student s1=new Student("zhangsan",18);
 
      Student s2=(Student)s1.clone();
 
      s2.name="lisi";
 
     s2.age=20;
 
System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2后,不影响
 
                                                                                                        //学生1的值。
 
   }




说明:


①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。


②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之。




class Professor 
 
{
 
    String name;
 
    int age;
 
    Professor(String name,int age)
 
    {
 
        this.name=name;
 
        this.age=age;
 
    }
 
}
 
class Student implements Cloneable
 
{
 
    String name;//常量对象。
 
    int age;
 
    Professor p;//学生1和学生2的引用值都是一样的。
 
    Student(String name,int age,Professor p)
 
    {
 
        this.name=name;
 
        this.age=age;
 
        this.p=p;
 
    }
 
    public Object clone()
 
    {
 
        Student o=null;
 
        try
 
        {
 
            o=(Student)super.clone();
 
        }
 
        catch(CloneNotSupportedException e)
 
        {
 
            System.out.println(e.toString());
 
        }
 
        o.p=(Professor)p.clone();
 
        return o;
 
    }
 
}
 
public static void main(String[] args)
 
    {
 
      Professor p=new Professor("wangwu",50);
 
      Student s1=new Student("zhangsan",18,p);
 
      Student s2=(Student)s1.clone();
 
      s2.p.name="lisi";
 
     s2.p.age=30;
 
System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授
 
                                                                                                                //成为lisi,age为30。
 
}


那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?代码改进如下。






改进使学生1的Professor不改变(深层次的克隆)



class Professor implements Cloneable
 
 
{
 
 
    String name;
 
 
    int age;
 
 
    Professor(String name,int age)
 
 
    {
 
 
        this.name=name;
 
 
        this.age=age;
 
 
    }
 
 
    public Object clone()
 
 
    {
 
 
        Object o=null;
 
 
        try
 
 
        {
 
 
            o=super.clone();
 
 
        }
 
 
        catch(CloneNotSupportedException e)
 
 
        {
 
 
            System.out.println(e.toString());
 
 
        }
 
 
        return o;
 
 
    }
 
 
}
 
 
class Student implements Cloneable
 
 
{
 
 
    String name;
 
 
    int age;
 
 
    Professor p;
 
 
    Student(String name,int age,Professor p)
 
 
    {
 
 
        this.name=name;
 
 
        this.age=age;
 
 
        this.p=p;
 
 
    }
 
 
    public Object clone()
 
 
    {
 
 
        Student o=null;
 
 
        try
 
 
        {
 
 
            o=(Student)super.clone();
 
 
        }
 
 
        catch(CloneNotSupportedException e)
 
 
        {
 
 
            System.out.println(e.toString());
 
 
        }
 
 
        o.p=(Professor)p.clone();
 
 
        return o;
 
 
    }
 
 
}
 
 
public static void main(String[] args)
 
 
    {
 
 
      Professor p=new Professor("wangwu",50);
 
 
      Student s1=new Student("zhangsan",18,p);
 
 
      Student s2=(Student)s1.clone();
 
 
      s2.p.name="lisi";
 
 
     s2.p.age=30;
 
 
System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授不改变。
 
 
}






3.利用串行化来做深复制



把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。



在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。



如下为深复制源代码。



public Object deepClone()
 
 
{
 
 
//将对象写到流里
 
 
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
 
 
ObjectOutputStream oo=new ObjectOutputStream(bo);
 
 
oo.writeObject(this);
 
 
//从流里读出来
 
 
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
 
 
ObjectInputStream oi=new ObjectInputStream(bi);
 
 
return(oi.readObject());
 
 
}






这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。






class Professor implements Serializable
 
 
{
 
 
    String name;
 
 
    int age;
 
 
    Professor(String name,int age)
 
 
    {
 
 
        this.name=name;
 
 
        this.age=age;
 
 
    }
 
 
}
 
 
class Student implements Serializable
 
 
{



    String name;//常量对象。



    int age;



    Professor p;//学生1和学生2的引用值都是一样的。



 

Student(String name,int age,Professor p)
 
 
    {
 
 
        this.name=name;
 
 
        this.age=age;
 
 
        this.p=p;
 
 
    }
 
 
    public Object deepClone() throws IOException,
 
 
OptionalDataException,ClassNotFoundException
 
 
{
 
 
//将对象写到流里
 
 
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
 
 
ObjectOutputStream oo=new ObjectOutputStream(bo);
 
 
oo.writeObject(this);
 
 
//从流里读出来
 
 
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
 
 
ObjectInputStream oi=new ObjectInputStream(bi);
 
 
return(oi.readObject());
 
 
}
 
 

 
 
}
 
 
public static void main(String[] args)
 
 
    {
 
 
      Professor p=new Professor("wangwu",50);
 
 
      Student s1=new Student("zhangsan",18,p);
 
 
      Student s2=(Student)s1.deepClone();
 
 
      s2.p.name="lisi";
 
 
     s2.p.age=30;
 
 
System.out.println("name="+s1.p.name+","+"age="+s1.p.age); //学生1的教授不改变。
 
 
}