Java中的深拷貝和淺拷貝

Java中的深拷貝和淺拷貝

2011-02-2811:08:45|分類:Java|字號訂閱

今天做項目時,碰到一個棘手的問題,就是需要把一個對象的屬性完全拷貝到另一個對象上.
其實當前項目中的這個對象是個簡單對象(即屬性不包括對其他對象的引用)
最後簡單的用淺拷貝完成了對象的拷貝.即在實體類中實現Clonable接口,實現publicObjectClone()方法.
通過調用父類的super.clone()方法(淺拷貝)可以重新生成一個對象,解決因對象引用賦值造成的原對象的修改.
如Students=newStudent("wangyan",23);
Students2=s;
s2.setName("ZhangSan");//實際上改的是Students,使得wangyan=>ZhangSan
而應該才用淺拷貝的方法
Students2=s.clone();
s2.setName("ZhangSan");
===============================================================================
深拷貝與淺拷貝的區別
1.淺複製與深複製概念
⑴淺複製(淺克隆)
被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不復制它所引用的對象。

⑵深複製(深克隆)
被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。

2.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接口。

請看如下代碼:


classStudentimplementsCloneable
{
Stringname;
intage;
Student(Stringname,intage)
{
this.name=name;
this.age=age;
}
publicObjectclone()
{
Objecto=null;
try
{
o=(Student)super.clone();//Object中的clone()識別出你要複製的是哪一
//個對象。
}
catch(CloneNotSupportedExceptione)
{
System.out.println(e.toString());
}
returno;
}


publicstaticvoidmain(String[]args)
{
Students1=newStudent("zhangsan",18);
Students2=(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()方法是淺複製。以下代碼可以證明之。

classProfessorimplementsCloneable
{
Stringname;
intage;
Professor(Stringname,intage)
{
this.name=name;
this.age=age;
}
publicObjectclone()throwsCloneNotSupportedException{
returnsuper.clone();
}
}
classStudentsimplementsCloneable
{
Stringname;//常量對象。
intage;
Professorp;//學生1和學生2的引用值都是一樣的。
Students(Stringname,intage,Professorp)
{
this.name=name;
this.age=age;
this.p=p;
}
publicObjectclone()
{
Studentso=null;
try
{
o=(Students)super.clone();
o.p=(Professor)p.clone();
}
catch(CloneNotSupportedExceptione)
{
System.out.println(e.toString());
}

returno;
}

publicstaticvoidmain(String[]args)
{
Professorp=newProfessor("wanGWu",50);
Studentss1=newStudents("zhangsan",18,p);
Studentss2=(Students)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不改變(深層次的克隆)

/**
*@authorsophiawang
*@since2009-3-20下午02:02:05
*類說明:
*/
classProfessorimplementsCloneable
{
Stringname;
intage;
Professor(Stringname,intage)
{
this.name=name;
this.age=age;
}
publicObjectclone()
{
Objecto=null;
try
{
o=super.clone();
}
catch(CloneNotSupportedExceptione)
{
System.out.println(e.toString());
}
returno;
}
}
classStudentimplementsCloneable
{
Stringname;
intage;
Professorp;
Student(Stringname,intage,Professorp)
{
this.name=name;
this.age=age;
this.p=p;
}
publicObjectclone()
{
Studento=null;
try
{
o=(Student)super.clone();
}
catch(CloneNotSupportedExceptione)
{
System.out.println(e.toString());
}
o.p=(Professor)p.clone();
returno;
}

publicstaticvoidmain(String[]args)
{
Professorp=newProfessor("wangwu",50);
Students1=newStudent("zhangsan",18,p);
Students2=(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接口,然後把對象(實際上只是對象的一個拷貝)寫到一個流裏(醃成鹹菜),再從流裏讀出來(把鹹菜回鮮),便可以重建對象。
如下爲深複製源代碼。
publicObjectdeepClone()throwsIOException,ClassNotFoundException
{
//將對象寫到流裏
ByteArrayOutputStreambo=newByteArrayOutputStream();
ObjectOutputStreamoo=newObjectOutputStream(bo);
oo.writeObject(this);
//從流裏讀出來
ByteArrayInputStreambi=newByteArrayInputStream(bo.toByteArray());
ObjectInputStreamoi=newObjectInputStream(bi);
return(oi.readObject());

}

這樣做的前提是對象以及對象內部所有引用到的對象都是可串行化的,否則,就需要仔細考察那些不可串行化的對象可否設成transient,從而將之排除在複製過程之外。上例代碼改進如下。

classProfessorimplementsSerializable{
Stringname;
intage;
Professor(Stringname,intage)
{
this.name=name;
this.age=age;
}
}
classStudentimplementsSerializable
{
Stringname;//常量對象。
intage;
Professorp;//學生1和學生2的引用值都是一樣的。
Student(Stringname,intage,Professorp)
{
this.name=name;
this.age=age;
this.p=p;
}
publicObjectdeepClone()throwsIOException,OptionalDataException,ClassNotFoundException{
//將對象寫到流裏
ByteArrayOutputStreambo=newByteArrayOutputStream();
ObjectOutputStreamoo=newObjectOutputStream(bo);
oo.writeObject(this);
//從流裏讀出來
ByteArrayInputStreambi=newByteArrayInputStream(bo.toByteArray());
ObjectInputStreamoi=newObjectInputStream(bi);
return(oi.readObject());
}


publicstaticvoidmain(String[]args)throwsOptionalDataException,IOException,ClassNotFoundException
{
Professorp=newProfessor("wangwu",50);
Students1=newStudent("zhangsan",18,p);
Students2=(Student)s1.deepClone();
s2.p.name="lisi";
s2.p.age=30;
System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//學生1的教授不改變。
}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章