如何正確運用 JAVA的Clone(淺克隆與深克隆)

如何正確運用 JAVA的Clone(淺克隆與深克隆)

當我們運用java的clone機制的時候 一定要結合實際需求合理運用,日前遇到一個關於clone的問題, 描述如下:


問題是這樣的

     當我們需要修改A對象的屬性值, 卻又想保留原有的A對象的一切, 這個時候我們會想到Clone。於是我們就調用了他的Clone方法(java.lang,Object的clone())。 但是這種clone是淺克隆。也就是說他只克隆當前的A對象裏面的基本類型的對象和引用對象這個引用值, 對於引用對象所指向的對象沒有在進行clone。 如上圖。 這樣要是code裏面有需求 去修改clone對象裏面  引用所指向的元素的值,如Array,List, Map等 vals[index] = n, map.put("key", value), list.remove(index) 諸如此類的修改操作。都會影響到被Clone的對象A。 看上圖一目瞭然。

    理論上我們需要實現java.lang.Cloneable, override的Clone()方法,把所有的引用對象 及其引用對象的子對象(直到最基本的對象類型) 都clone一邊。這樣是最保險的。 但是,有的時候結合實際需求,只深clone那些 可能會被修改的元素,那些完全沒有可能和不允許修改的(只讀的)引用對象,可以不必也進行深克隆。這樣簡化複雜度。

    比如上圖例子,如果List<List>所引用的對象不會有被修改的可能,那麼我們可以不必遍歷list的每一個對象進行clone。如果Map所引用的對象,它的鍵值對裏面的value可能會被修改, 那麼我們需要clone這個map的所有子元素 去構建一個新的Map。

 簡單實驗:

 CODE:

    public class TestHashMapClone {
        public static void main(String[] args) {  


            //HashMap has overrided clone()
            HashMap source = new HashMap(); 
            source.put("key1","value1"); 
            source.put("key2","value2"); 
             
            for(Iterator keyItr = source.keySet().iterator();keyItr.hasNext();) { 
                Object key = keyItr.next(); 
                System.out.println(key + " : "+source.get(key)); 
            } 
            System.out.println("---------------- 1 ----------------"); 
             
            Map targetMap = (HashMap)source.clone(); 
      
            for(Iterator keyItr = targetMap.keySet().iterator();keyItr.hasNext();){ 
                Object key = keyItr.next(); 
                System.out.println(key + " : "+source.get(key)); 
            } 
             
            System.out.println("---------------- 2 ----------------"); 
             
            Map targetMap1 = (HashMap)source.clone();
           
            Object temp = targetMap1.put("key1","modify"); 
            System.out.println("temp : "+temp); 
             
            for(Iterator keyItr = source.keySet().iterator();keyItr.hasNext();){ 
                Object key = keyItr.next(); 
                System.out.println(key + " : "+source.get(key)); 
            } 
           
            System.out.println("---------------- 3 ----------------"); 
           
            HashMap source1 = new HashMap(); 
            source1.put("key1","value1"); 
            source1.put("key2","value2");
            Map targetMap2 = (HashMap)source1.clone();
           
            Object temp2 = source1.put("key1","modify"); 
            System.out.println("temp : "+temp2); 
             
            for(Iterator keyItr = targetMap2.keySet().iterator();keyItr.hasNext();){ 
                Object key = keyItr.next(); 
                System.out.println(key + " : "+targetMap2.get(key)); 
            }
           
            System.out.println("---------------- 4 ----------------");
           
            HashMap source2 = new HashMap();
           
            List list1 = new ArrayList<String>();
            list1.add("value1");
            list1.add("value2");
            list1.add("value3");
           
            List list2 = new ArrayList<String>();
            list2.add("value1");
            list2.add("value2");
            list2.add("value3");
           
            source2.put("key1",list1); 
            source2.put("key2",list2);
           
            Map targetMap3 = (HashMap)source2.clone();
           
            list1.set(0, "modify0");
           
            System.out.println();
            for(Iterator keyItr = source2.keySet().iterator();keyItr.hasNext();){ 
                Object key = keyItr.next();
                List<String> list = (List<String>)source2.get(key);
                for(String value : list) {
                    System.out.println(key + " : "+ value); 
                }
            }
       
            System.out.println();
            for(Iterator keyItr = targetMap3.keySet().iterator();keyItr.hasNext();){ 
                Object key = keyItr.next();
                List<String> list = (List<String>)targetMap3.get(key);
                for(String value : list) {
                    System.out.println(key + " : "+ value); 
                }
            }
        } 
}    

實驗結果

key2 : value2
key1 : value1
---------------- 1 ----------------
key2 : value2
key1 : value1
---------------- 2 ----------------
temp : value1
key2 : value2
key1 : value1
---------------- 3 ----------------
temp : value1
key2 : value2
key1 : value1
---------------- 4 ----------------

key2 : value1
key2 : value2
key2 : value3
key1 : modify0
key1 : value2
key1 : value3

key2 : value1
key2 : value2
key2 : value3
key1 : modify0
key1 : value2
key1 : value3

   

總結:

    注意如下幾點即可:

     a. 什麼時候使用shallow Clone,什麼時候使用deep Clone,這個主要看具體對象的域是什麼性質的,基本型別還是reference variable ,以及實際的需求。

      b. 調用Clone()方法的對象所屬的類(Class)必須implements Cloneable接口,否則在調用Clone方法的時候會拋出CloneNotSupportedException。 

      c. 一般集合類都是淺clone,這個可以看一下他的源碼。


【以下內容轉載自:http://lovelace.iteye.com/blog/182772

Java語言的一個優點就是取消了指針的概念,但也導致了許多程序員在編程中常常忽略了對象與引用的區別,特別是先學c、c++後學java的程序員。並且由於Java不能通過簡單的賦值來解決對象複製的問題,在開發過程中,也常常要要應用clone()方法來複制對象。比如函數參數類型是自定義的類時,此時便是引用傳遞而不是值傳遞。以下是一個小例子:

Java代碼  收藏代碼
  1. public class A {  
  2.     public String name;  
  3. }  

 

Java代碼  收藏代碼
  1. public class testClone {  
  2.       
  3.     public void changeA(A a){  
  4.         a.name="b";  
  5.     }  
  6.     public void changInt(int i){  
  7.         i=i*2+100;  
  8.     }  
  9.       
  10.     /** 
  11.      * @param args 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         // TODO Auto-generated method stub  
  15.         testClone test=new testClone();  
  16.         A a=new A();  
  17.         a.name="a";  
  18.         System.out.println("before change : a.name="+a.name);  
  19.         test.changeA(a);  
  20.         System.out.println("after  change : a.name="+a.name);  
  21.         int i=1;  
  22.         System.out.println("before change : i="+i);  
  23.         test.changInt(i);  
  24.         System.out.println("after  change : i="+i);  
  25.     }  
  26.   
  27. }  



此時輸出的結果是:

Java代碼  收藏代碼
  1. before change : a.name=a  
  2. after  change : a.name=b  
  3. before change : i=1  
  4. after  change : i=1  



從這個例子知道Java對對象和基本的數據類型的處理是不一樣的。在Java中用對象的作爲入口參數的傳遞則缺省爲"引用傳遞",也就是說僅僅傳遞了對象的一個"引用",這個"引用"的概念同C語言中的指針引用是一樣的。當函數體內部對輸入變量改變時,實質上就是在對這個對象的直接操作。
除了在函數傳值的時候是"引用傳遞",在任何用"="向對象變量賦值的時候都是"引用傳遞",如:

Java代碼  收藏代碼
  1. A a1=new A();  
  2. A a2=new A();  
  3. a1.name="a1";  
  4. a2=a1;  
  5. a2.name="a2";  
  6. System.out.println("a1.name="+a1.name);  
  7. System.out.println("a2.name="+a2.name);  



此時輸出的結果是:

Java代碼  收藏代碼
  1. a1.name=a2  
  2. a2.name=a2  



如果我們要用a2保存a1對象的數據,但又不希望a2對象數據被改變時不影響到a1。實現clone()方法是其一種最簡單,也是最高效的手段。
下面我們來實現A的clone方法

Java代碼  收藏代碼
  1. public class A implements Cloneable {  
  2.     public String name;  
  3.   
  4.     public Object clone() {  
  5.         A o = null;  
  6.         try {  
  7.             o = (A) super.clone();  
  8.         } catch (CloneNotSupportedException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.         return o;  
  12.     }  
  13.   
  14. }  


首先要實現Cloneable接口,然後在重載clone方法,最後在clone()方法中調用了super.clone(),這也意味着無論clone類的繼承結構是什麼樣的,super.clone()直接或間接調用了java.lang.Object類的clone()方法。

Java代碼  收藏代碼
  1. A a1=new A();  
  2. A a2=new A();  
  3. a1.name="a1";  
  4. a2=a1;  
  5. a2.name="a2";  
  6. System.out.println("a1.name="+a1.name);  
  7. System.out.println("a2.name="+a2.name);  



此時輸出的結果是:

Java代碼  收藏代碼
  1. a1.name=a1  
  2. a2.name=a2  



當Class A成員變量類型是java的基本類型時(外加String類型),只要實現如上簡單的clone(稱影子clone)就可以。但是如果Class A成員變量是數組或複雜類型時,就必須實現深度clone。

Java代碼  收藏代碼
  1. public class A implements Cloneable {  
  2.     public String name[];  
  3.       
  4.     public A(){  
  5.         name=new String[2];  
  6.     }  
  7.   
  8.     public Object clone() {  
  9.         A o = null;  
  10.         try {  
  11.             o = (A) super.clone();  
  12.         } catch (CloneNotSupportedException e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.         return o;  
  16.     }  
  17. }  


測試代碼

Java代碼  收藏代碼
  1. A a1=new A();  
  2. A a2=new A();  
  3. a1.name[0]="a";  
  4. a1.name[1]="1";  
  5. a2=(A)a1.clone();  
  6. a2.name[0]="b";  
  7. a2.name[1]="1";  
  8. System.out.println("a1.name="+a1.name);  
  9. System.out.println("a1.name="+a1.name[0]+a1.name[1]);  
  10. System.out.println("a2.name="+a2.name);  
  11. System.out.println("a2.name="+a2.name[0]+a2.name[1]);  


輸出結果:

Java代碼  收藏代碼
  1. a1.name=[Ljava.lang.String;@757aef  
  2. a1.name=b1  
  3. a2.name=[Ljava.lang.String;@757aef  
  4. a2.name=b1  


看到了吧,a1.name,a2.name的hash值都是@757aef,也就是說影子clone對name數組只是clone他們的地址!解決該辦法是進行深度clone。

Java代碼  收藏代碼
  1. public Object clone() {  
  2.         A o = null;  
  3.         try {  
  4.             o = (A) super.clone();  
  5.             o.name=(String[])name.clone();//其實也很簡單^_^  
  6.         } catch (CloneNotSupportedException e) {  
  7.             e.printStackTrace();  
  8.         }  
  9.         return o;  
  10.     }  


此時輸出結果是:

Java代碼  收藏代碼
  1. a1.name=[Ljava.lang.String;@757aef  
  2. a1.name=a1  
  3. a2.name=[Ljava.lang.String;@d9f9c3  
  4. a2.name=b1  


需要注意的是Class A存在更爲複雜的成員變量時,如Vector等存儲對象地址的容器時,就必須clone徹底。

Java代碼  收藏代碼
  1. public class A implements Cloneable {  
  2.     public String name[];  
  3.     public Vector<B> claB;  
  4.       
  5.     public A(){  
  6.         name=new String[2];  
  7.         claB=new Vector<B>();  
  8.     }  
  9.   
  10.     public Object clone() {  
  11.         A o = null;  
  12.         try {  
  13.             o = (A) super.clone();  
  14.             o.name==(String[])name.clone();//深度clone  
  15.             o.claB=new Vector<B>();//將clone進行到底  
  16.             for(int i=0;i<claB.size();i++){  
  17.                 B temp=(B)claB.get(i).clone();//當然Class B也要實現相應clone方法  
  18.                 o.claB.add(temp);  
  19.             }  
  20.         } catch (CloneNotSupportedException e) {  
  21.             e.printStackTrace();  
  22.         }  
  23.                 return o;  
  24.     }  
  25. }  

 


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