java克隆(深淺拷貝,複製)詳解

1.淺複製與深複製概念

(1)淺複製(淺克隆)被複制對象的所有變量都含有與原來對象相同的值,而所有的對其他對象的引用仍然只指向原來的對象,換言之,淺複製僅僅複製鎖考慮的對象,而不復制它所引用的對象。(2)深複製(深克隆)被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量,那些引用其他對象的變量將指向被複制過的新對象,而不再試原有的那些被引用的對象,換言之,深複製把要複製的對象所引用的對象都複製了一遍。

實現ICloneable接口的方式取決於我們的類型的數據成員。如果類型僅包含值類型(int,byte等類型)和string類型的數據成員,我們只要在Clone方法中初始化一個新的對象,將其的數據成員設置爲當前對象的各個成員的值即可。事實上,object類的MemberwiseClone方法會自動完成該過程。

如果自定義類型包含引用類型的數據成員,必須考慮Clone方法是實現淺拷貝(shallow copy)還是深拷貝(deep copy)。淺拷貝是指副本對象中的引用類型的數據成員與源對象的數據成員指向相同的對象。而如果是深拷貝,則必須創建整個對象的結構,副本對象中的引用類型的數據成員與源對象的數據成員指向不同的對象。

淺拷貝是容易實現的,就是使用前面提到的MemberwiseClone方法。開發人員往往希望使用的類型能夠實現深拷貝,但會發現這樣的類型並不多。這種情況在System.Collections命名空間中尤其常見,這裏面的類在其Clone方法中實現的都是淺拷貝

 

2. java的clone()方法


(1)clone方法將對象複製了一份並返回給調用之,一般而言,clone()方法滿足:
     對任何的對象C=X,都有x.clone()!=x//克隆對象與源對象不是同一個對象
     對任何的對象X,都有x.clone().getClass()==x.getClass()//克隆對象與源對象類型一樣
     如果對象X的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。

(2)java對象中的克隆
    爲了獲取對象的一份拷貝,我們可以利用Object類得clone()方法。
    在派生類中覆蓋基類的clone(),並聲明爲public。
    在派生類的clone()方法中,調用super.clone().
    在派生類中實現Cloneable接口
代碼如下 

package clone;

public 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("zhang",18);
  Student s2 = (Student)s1.clone();
  s2.name="li";
  s2.age=20;
  System.out.println("name="+s1.name+","+"age="+s1.age);//修改學生2後不影響學生1的值
   }
}

說明:
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();//只是此處編譯錯誤clone是Object類的一個protected方法
        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。
}

 此處淺拷貝的測試代碼如果要運行正確需要把professor類中的o.p=(Professor)p.clone();去掉

package clone;

public class CloneTestPro  
{  
    public static void main(String[] args)  
    {  
     Professor p = new Professor("王教授",50);
        Student s1 = new Student("張學生",18,p); 
        System.out.println("s1:" + s1.name + " " + s1.age + " "   + s1.p.name+ " "   + s1.p.age);  
          
            //拷貝一個學生   
        Student s2 = (Student)s1.clone();  
        s2.age=21;  
        s2.name="WUDI";  
        System.out.println("s2:" + s2.name + " " + s2.age + " "+ s2.p.name+ " " + s2.p.age);  
        System.out.println("after s2 changing some property:");  
        //通過學生s2改變了他所在學校的名稱和成立時間  
        s2.name="Tianjin University";  
        s2.age=12;  
        System.out.println("s1:" + s1.name + " " + s1.age + " "  + s1.p.name+ " "   + s1.p.age);  
        System.out.println("s2:" + s2.name + " " + s2.age + " " + s2.p.name+ " "  + s2.p.age);  
        //從運行結果可以看出,通過學生s2將其引用school所指向的對象的name和bornYear屬性改變,
        //相應的s1的也被改變,這說明學生s1和學生s2裏面的school引用指向的始終是同一個School對象。這就是淺拷貝的含義。
    }  
}  

 

2.那應該如何實現深層次的克隆,即修改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的教授不改變。
}

(1)淺拷貝
默認的拷貝都是淺拷貝,你可以通過下面的方法實現:

Java代碼

class Employee implements Cloneable  

{  

   public Employee clone() throws CloneNotSupportedException  

    {  

     return super.clone();  

    }  

class Employee implements Cloneable

{

   public Employee clone() throws CloneNotSupportedException

    {

     return super.clone();

    }

}


淺拷貝需要記住的是原對象的各屬性最好都是基本類型或者不可變的對象。

(2)深拷貝
深拷貝是相對淺拷貝的,解決不能管理子對象的問題的。
實現方法如下:

Java代碼

class Employee implements Cloneable  

{  

   public Object clone() throws CloneNotSupportedException  

    {  

     Employee cloned = (Employee)super.clone();  

    //深拷貝子對象  

     cloned.dep = (Department)dep.clone();  

     return cloned.  

    }  

 

  值得注意的是 :如果需要使用clone方法,必需實現java.lang.Cloneable接口,否則會拋出java.lang.CloneNotSupportedException。

  另外clone方法所做的的操作是直接複製字段的內容,換句話說,這個操作並不管該字段對應的對象實例內容。

  像這樣字段對字段的拷貝(field to field copy)就成爲"淺拷貝",clone方法所做的正是"淺拷貝"。

 

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的教授不改變。
}
 

 

 

發佈了29 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章