java中的深拷貝和淺拷貝
做專案時,可能會碰到這樣的一個問題,就是需要把一個物件的屬性完全拷貝到另一個物件上.
當這個物件是個簡單物件(即屬性不包括對其他物件的引用)時
用淺拷貝來完成物件的拷貝.即在實體類中實現Clonable介面,實現 public Object Clone()方法.
通過呼叫父類的super.clone()方法(淺拷貝)可以重新生成一個物件,解決因物件引用賦值造成的原物件的修改.
如 Student s=new Student("wangyan",23);
Student s2=s;
s2.setName("ZhangSan"); //實際上改的是Student s, 使得 wangyan=>ZhangSan
而應該才用淺拷貝的方法
Student s2=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介面。
請看如下程式碼:
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 implements Cloneable
{
String name;
int age;
Professor(String name,int age)
{
this.name=name;
this.age=age;
}
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
class Students implements Cloneable
{
String name;//常量物件。
int age;
Professor p;//學生1和學生2的引用值都是一樣的。
Students(String name,int age,Professor p)
{
this.name=name;
this.age=age;
this.p=p;
}
public Object clone()
{
Students o=null;
try
{
o=(Students)super.clone();//只是對Student克隆,P並未克隆
}
catch(CloneNotSupportedException e)
{
System.out.println(e.toString());
}
return o;
}
public static void main(String[] args)
{
Professor p=new Professor("wanGWu",50);
Students s1=new Students("zhangsan",18,p);
Students s2=(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不改變(深層次的克隆)
/**
* @author sophia wang
* @since 2009-3-20 下午02:02:05
* 類說明:
*/
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() throws IOException, ClassNotFoundException
{
//將物件寫到流裡
ByteArrayOutputStream 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{
//將物件寫到流裡
ByteArrayOutputStream 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) throws OptionalDataException, IOException, ClassNotFoundException
{
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的教授不改變。
}
}