當我們運用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()方法來複制對象。比如函數參數類型是自定義的類時,此時便是引用傳遞而不是值傳遞。以下是一個小例子:
- public class A {
- public String name;
- }
- public class testClone {
- public void changeA(A a){
- a.name="b";
- }
- public void changInt(int i){
- i=i*2+100;
- }
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- testClone test=new testClone();
- A a=new A();
- a.name="a";
- System.out.println("before change : a.name="+a.name);
- test.changeA(a);
- System.out.println("after change : a.name="+a.name);
- int i=1;
- System.out.println("before change : i="+i);
- test.changInt(i);
- System.out.println("after change : i="+i);
- }
- }
此時輸出的結果是:
- before change : a.name=a
- after change : a.name=b
- before change : i=1
- after change : i=1
從這個例子知道Java對對象和基本的數據類型的處理是不一樣的。在Java中用對象的作爲入口參數的傳遞則缺省爲"引用傳遞",也就是說僅僅傳遞了對象的一個"引用",這個"引用"的概念同C語言中的指針引用是一樣的。當函數體內部對輸入變量改變時,實質上就是在對這個對象的直接操作。
除了在函數傳值的時候是"引用傳遞",在任何用"="向對象變量賦值的時候都是"引用傳遞",如:
- A a1=new A();
- A a2=new A();
- a1.name="a1";
- a2=a1;
- a2.name="a2";
- System.out.println("a1.name="+a1.name);
- System.out.println("a2.name="+a2.name);
此時輸出的結果是:
- a1.name=a2
- a2.name=a2
如果我們要用a2保存a1對象的數據,但又不希望a2對象數據被改變時不影響到a1。實現clone()方法是其一種最簡單,也是最高效的手段。
下面我們來實現A的clone方法
- public class A implements Cloneable {
- public String name;
- public Object clone() {
- A o = null;
- try {
- o = (A) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
首先要實現Cloneable接口,然後在重載clone方法,最後在clone()方法中調用了super.clone(),這也意味着無論clone類的繼承結構是什麼樣的,super.clone()直接或間接調用了java.lang.Object類的clone()方法。
- A a1=new A();
- A a2=new A();
- a1.name="a1";
- a2=a1;
- a2.name="a2";
- System.out.println("a1.name="+a1.name);
- System.out.println("a2.name="+a2.name);
此時輸出的結果是:
- a1.name=a1
- a2.name=a2
當Class A成員變量類型是java的基本類型時(外加String類型),只要實現如上簡單的clone(稱影子clone)就可以。但是如果Class A成員變量是數組或複雜類型時,就必須實現深度clone。
- public class A implements Cloneable {
- public String name[];
- public A(){
- name=new String[2];
- }
- public Object clone() {
- A o = null;
- try {
- o = (A) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
測試代碼
- A a1=new A();
- A a2=new A();
- a1.name[0]="a";
- a1.name[1]="1";
- a2=(A)a1.clone();
- a2.name[0]="b";
- a2.name[1]="1";
- System.out.println("a1.name="+a1.name);
- System.out.println("a1.name="+a1.name[0]+a1.name[1]);
- System.out.println("a2.name="+a2.name);
- System.out.println("a2.name="+a2.name[0]+a2.name[1]);
輸出結果:
- a1.name=[Ljava.lang.String;@757aef
- a1.name=b1
- a2.name=[Ljava.lang.String;@757aef
- a2.name=b1
看到了吧,a1.name,a2.name的hash值都是@757aef,也就是說影子clone對name數組只是clone他們的地址!解決該辦法是進行深度clone。
- public Object clone() {
- A o = null;
- try {
- o = (A) super.clone();
- o.name=(String[])name.clone();//其實也很簡單^_^
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
此時輸出結果是:
- a1.name=[Ljava.lang.String;@757aef
- a1.name=a1
- a2.name=[Ljava.lang.String;@d9f9c3
- a2.name=b1
需要注意的是Class A存在更爲複雜的成員變量時,如Vector等存儲對象地址的容器時,就必須clone徹底。
- public class A implements Cloneable {
- public String name[];
- public Vector<B> claB;
- public A(){
- name=new String[2];
- claB=new Vector<B>();
- }
- public Object clone() {
- A o = null;
- try {
- o = (A) super.clone();
- o.name==(String[])name.clone();//深度clone
- o.claB=new Vector<B>();//將clone進行到底
- for(int i=0;i<claB.size();i++){
- B temp=(B)claB.get(i).clone();//當然Class B也要實現相應clone方法
- o.claB.add(temp);
- }
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }