java中有關深拷貝和淺拷貝的解析

 熟悉C++的朋友對這個話題應該很熟悉,淺拷貝就是指兩個對象共同擁有同一個值,一個對象改變了該值,也會影響到另一個對象。深拷貝就是兩個對象的值相等,但是互相獨立。本來想把以前寫的一篇文章擴充一下,沒想到居然牽扯出很多複雜的問題。本文測試環境是windows xp sp3中文版、NetBeans6.7.1,JDK1.6-update16。這裏拋磚引玉,希望大家能提寶貴意見。

    首先,Java中常用的拷貝操作有三個,operator = 、拷貝構造函數 和 clone()方法。由於Java不支持運算符重載,我們無法在自己的自定義類型中定義operator=。拷貝構造函數大家應該很熟悉,現在看一下如何支持clone方法:

  1. 實現 Cloneable接口,因爲 Object的 clone方法將檢查類是否實現了 Cloneable接口,如果沒有將拋出異常 CloneNotSupportedException對象。 Cloneable接口沒有任何方法,只是個標誌,所以只需要簡單的寫上 implements Cloneable即可。

  2. 改寫從 Object繼承而來的 clone方法,使它的訪問權限爲 public,因爲爲了防止意外的支持 clone操作, Object的 clone方法是 protected權限。


    通過上面的分析,可以看出,如果我們要給自己的類添加拷貝功能,我們可以添加拷貝構造函數和實現Cloneable接口。
    現在,來看一下不同的類型在拷貝過程中的表現:


Operator =
拷貝構造函數
clone方法
預定義非集合類型
深拷貝
如果支持拷貝構造函數的類型,則是深拷貝
不支持
自定義類型
淺拷貝
取決於實現
取決於實現
預定義集合類型
淺拷貝
會逐個調用每個元素的operator=方法
會逐個調用每個元素的operator=方法


    下面是測試代碼,首先測試的是預定義非集合類型的operator =操作:

  1. int x=1;  
  2.        int y=x;  
  3.        y=2;  
  4.        if(x!=y){  
  5.            System.out.println("deep copy");  
  6.        }  
  7.   
  8.   
  9.        Integer a=1;  
  10.        Integer b=a;  
  11.        b=2;  
  12.        if(!a.equals(b)){  
  13.            System.out.println("deep copy");  
  14.        }  
  15.   
  16.   
  17.        String m="ok";  
  18.        String n=m;  
  19.        n="no";  
  20.        if(!m.equals(n)){  
  21.            System.out.println("deep copy");  
  22.        }  

 程序運行後,輸出三行deep copy,測試結果表明,這三種類型的operator =操作都是深拷貝。由於我沒有測試完所有的預定義非集合類型,我這裏推測它們的operator =都是深拷貝。

    下面測試預定義非集合類型的拷貝構造函數:

  1. Integer a=1;  
  2.        Integer b=new Integer(a);  
  3.        b=2;  
  4.        if(!a.equals(b)){  
  5.            System.out.println("deep copy");  
  6.        }  
  7.   
  8.   
  9.        String m="ok";  
  10.        String n=new String(m);  
  11.        n="no";  
  12.        if(!m.equals(n)){  
  13.            System.out.println("deep copy");  
  14.        }  

程序運行後,輸出兩行deep copy,測試結果表明,這兩種類型的拷貝構造函數都是深拷貝。int沒有拷貝構造函數。
    
        現在我們來測試自定義類型的operator=操作,假設我有一個類Person,代碼如下:

  1. public class Person implements Cloneable{  
  2.     private int age;  
  3.     private String name;  
  4.     public int getAge() {  
  5.         return age;  
  6.     }  
  7.     public void setAge(int age) {  
  8.         this.age = age;  
  9.     }  
  10.     public String getName() {  
  11.         return name;  
  12.     }  
  13.     void setName(String name) {  
  14.         this.name = name;  
  15.     }  
  16. }  

 測試代碼:

  1. Person p=new Person();  
  2.        p.setAge(32);  
  3.        p.setName("陳抒");  
  4.   
  5.        Person p2=p;  
  6.        p.setAge(33);  
  7.        p.setName("老陳");  
  8.   
  9.        if( (p.getAge()!=p2.getAge())&&(!p.getName().equals(p2.getName())) ){  
  10.            System.out.println("deep copy");  
  11.        }  

 運行後,沒有輸出deep copy,說明這是淺拷貝。這裏就是我們常說的兩個引用之間的賦值,僅僅是讓兩個引用指向同一個對象。

    現在,我們來測試預定義集合類型的operator=操作:

  1. ArrayList list1=new ArrayList();  
  2.         list1.add("yangzhou");  
  3.         ArrayList list2=list1;  
  4.         list1.clear();  
  5.   
  6.         if(list2.isEmpty()){  
  7.             System.out.println("shallow copy");  
  8.         }  

 結果輸出爲shallow copy。

    現在我來測試拷貝構造函數:

  1. ArrayList list1=new ArrayList();  
  2.         list1.add("yangzhou");  
  3.         ArrayList list2=new ArrayList(list1);  
  4.         list1.clear();  
  5.   
  6.         if(list2.isEmpty()){  
  7.             System.out.println("shallow copy");  
  8.         }else{  
  9.             System.out.println("deep copy");  
  10.         }  

輸出結果是deep copy;
    clone方法的測試代碼只是將第三行換成list1.clone(),加上類型轉換,這裏不再貼代碼了。結果也證明是深拷貝 。
    預定義集合類的深拷貝 實際上就是調用每個元素的operator =。如果元素都是自定義類型的化,實際上還是淺拷貝。現在來看測試代碼:

  1. ArrayList list1=new ArrayList();  
  2.        Person p1=new Person();  
  3.        p1.setAge(32);  
  4.        p1.setName("陳抒");  
  5.        list1.add(p1);  
  6.   
  7.        ArrayList list2=(ArrayList) list1.clone();  
  8.        list2.get(0).setName("chenshu");  
  9.   
  10.        if(list2.get(0).getName().equals(list1.get(0).getName())){  
  11.            System.out.println("shallow copy");  
  12.        }else{  
  13.            System.out.println("deep copy");  
  14.   
  15.        }  

 輸出爲shallow copy,Person是自定義類型,它的operator =運算符只是引用之間賦值,是淺拷貝。因此當修改了list2的第一個元素指向的Person對象的name屬性,也就是修改了list1第一個元素所指向的Person對象的name屬性。對於這種拷貝,我自己起了一個名字,叫做第一層深拷貝。
    
    現在我們有了表格中的結論,自己實現拷貝構造函數或者clone方法的時候就心裏有數多了。
    假如我的自定義類型內部成員變量都是預定義非集合類型,那麼在clone方法中只需要調用Object.clone即可完成深拷貝操作。在拷貝構造函數中需要使用operator=來一個個的深拷貝;
    假如我們的自定義類型內部成員變量有一些預定義類型,另一些是自定義類型,如果要深拷貝的話,最好調用自定義類型成員變量的拷貝構造函數或者clone方法。下面是例子代碼:

  1. public class Company {  
  2.     public Company(){  
  3.   
  4.     }  
  5.   
  6.     public Company(Company c){  
  7.         name=c.name;  
  8.         person=new Person(c.person);  
  9.     }  
  10.     private String name;  
  11.     private Person person;  
  12.   
  13.     public Person getPerson() {  
  14.         return person;  
  15.     }  
  16.   
  17.     public void setPerson(Person person) {  
  18.         this.person = person;  
  19.     }  
  20.   
  21.     public String getName() {  
  22.         return name;  
  23.     }  
  24.   
  25.     public void setName(String name) {  
  26.         this.name = name;  
  27.     }  
  28.   
  29.     @Override  
  30.     public Object clone() throws CloneNotSupportedException{  
  31.         Company c=new Company();  
  32.         c.setName(name);  
  33.         c.setPerson((Person) person.clone());  
  34.         return c;  
  35.     }  
  36. }  
  37.   
  38. public class Person implements Cloneable{  
  39.     public Person(){  
  40.   
  41.     }  
  42.   
  43.     public Person(Person p){  
  44.         age=p.age;  
  45.         name=p.name;  
  46.     }  
  47.     private int age;  
  48.     private String name;  
  49.     public int getAge() {  
  50.         return age;  
  51.     }  
  52.     public void setAge(int age) {  
  53.         this.age = age;  
  54.     }  
  55.   
  56.     public String getName() {  
  57.         return name;  
  58.     }  
  59.   
  60.     public void setName(String name) {  
  61.         this.name = name;  
  62.     }  
  63.   
  64.       
  65.     @Override  
  66.     public Object clone() throws CloneNotSupportedException{  
  67.         return super.clone();  
  68.     }  
  69. }  

Person類的兩個成員變量都是預定義非集合類型,所以只需要在clone方法中簡單的調用super.clone()即可實現深拷貝。Company類有一個Person成員變量,因此要調用Person的clone方法
發佈了21 篇原創文章 · 獲贊 0 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章