1. 程式人生 > 其它 >Java基礎——clone()方法淺析

Java基礎——clone()方法淺析

一、clone的概念 

  clone顧名思義就是複製, 在Java語言中, clone方法被物件呼叫,所以會複製物件。所謂的複製物件,首先要分配一個和源物件同樣大小的空間,在這個空間中建立一個新的物件。那麼在java語言中,有幾種方式可以建立物件呢? 

  1. 使用new操作符建立一個物件
  2. 使用clone方法複製一個物件 

  那麼這兩種方式有什麼相同和不同呢?

  • new操作符的本意是分配記憶體。程式執行到new操作符時, 首先去看new操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化,構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。 1 Person p = new Person(23, "zhang"); 2 Person p1 = p; 3 4 System.out.println(p); 5 System.out.println(p1);  當Person p1 = p;執行之後, 是建立了一個新的物件嗎? 首先看列印結果:   com.pansoft.zhangjg.testclone.Person@2f9ee1ac   com.pansoft.zhangjg.testclone.Person@2f9ee1ac 可以看出,列印的地址值是相同的,既然地址都是相同的,那麼肯定是同一個物件。p和p1只是引用而已,他們都指向了一個相同的物件Person(23, "zhang") 。 可以把這種現象叫做引用的複製
    。 
  • 而clone在第一步是和new相似的, 都是分配記憶體,呼叫clone方法時,分配的記憶體和源物件(即呼叫clone方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域, 填充完成之後,clone方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。注意:如果一個類沒有複寫clone()方法,則clone()方法預設的是返回一個Object物件,我們可以強制轉化為我們需要的類。 Person p = new Person(23, "zhang"); Person p1 = (Person) p.clone(); System.out.println(p); System.out.println(p1);  從列印結果可以看出,兩個物件的地址是不同的,也就是說建立了新的物件, 而不是把原物件的地址賦給了一個新的引用變數:    com.pansoft.zhangjg.testclone.Person@2f9ee1ac   com.pansoft.zhangjg.testclone.Person@67f1fba0

二、clone的用法

  Java程式設計中經常會用到clone()方法,clone()方法是Object類的一個protected的方法,由於非靜態所以不能直接呼叫,但是可以被子類呼叫。Object類的clone()方法會知道物件大小,為其分配足夠的記憶體空間,並將其物件的內容複製到新的物件中。但是,clone()方法在執行其動作之前必須先檢查class是否實現了Cloneable介面。Cloneable介面和Serialiable介面一樣是一個標記介面,沒有任何內容。

Java中clone()方法的含義是,假設x是一個非空的物件,則:

    x.clone() != x                  為true

,就是說他們不是同一個物件

    x.clone(),getClass() == x.getClass()      為true,說明他們是同一型別的class

    x.equals(x.clone())                為true,說明邏輯上應該相同

  一個類的物件要想在呼叫clone()方法時不丟擲CloneNotSupportedException,有兩種方法:

  1. 該類在定義宣告時宣告實現了Cloneable介面,即在宣告時加上“implements Cloneable”即可(淺拷貝
  2. 該類在定義時,宣告實現Cloneable介面,並且複寫clone()方法,並將其宣告為public(深拷貝

三、淺析clone()方法與淺拷貝、深拷貝概念

  在上面一節中我們發現正確使用clone()方法的兩種方法的結果並不相同,直接實現Cloneable介面只是實現了淺拷貝,而複寫clone()方法則可以實現深拷貝。那麼,這種copy有什麼區別呢?

  上面的示例程式碼中,Person中有兩個成員變數,分別是name和age, name是String型別, age是int型別。程式碼非常簡單,如下所示:

 1 public class Person implements Cloneable{
 2      
 3     private int age ;
 4     private String name;
 5      
 6     public Person(int age, String name) {
 7         this.age = age;
 8         this.name = name;
 9     }
10      
11     public Person() {}
12  
13     public int getAge() {
14         return age;
15     }
16  
17     public String getName() {
18         return name;
19     }
20      
21     @Override
22     protected Object clone() throws CloneNotSupportedException {
23         return (Person)super.clone();
24     }
25 }

  由於age是基本資料型別, 那麼對它的拷貝沒有什麼疑議,直接將一個4位元組的整數值拷貝過來就行。但是name是String型別的, 它只是一個引用, 指向一個真正的String物件,那麼對它的拷貝有兩種方式: 直接將源物件中的name的引用值拷貝給新物件的name欄位, 或者是根據原Person物件中的name指向的字串物件建立一個新的相同的字串物件,將這個新字串物件的引用賦給新拷貝的Person物件的name欄位。這兩種拷貝方式分別叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如下圖所示:

  下面通過程式碼進行驗證。如果兩個Person物件的name的地址值相同, 說明兩個物件的name都指向同一個String物件, 也就是淺拷貝, 而如果兩個物件的name的地址值不同, 那麼就說明指向不同的String物件, 也就是在拷貝Person物件的時候, 同時拷貝了name引用的String物件, 也就是深拷貝。驗證程式碼如下:

Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();
 
System.out.println("p.getName().hashCode() : " + p.getName().hashCode());
System.out.println("p1.getName().hashCode() : " + p1.getName().hashCode());
 
String result = p.getName().hashCode() == p1.getName().hashCode() 
        ? "clone是淺拷貝的" : "clone是深拷貝的";
 
System.out.println(result);

列印結果為:

  p.getName().hashCode() : 115864556   p1.getName().hashCode() : 115864556   clone是淺拷貝的

  所以,clone方法執行的是淺拷貝, 在編寫程式時要注意這個細節。 

四、深拷貝的實現

  為了要在clone物件時進行深拷貝, 那麼就要實現Clonable介面,覆蓋並實現clone方法,除了呼叫父類中的clone方法得到新的物件, 還要將該類中的引用變數也clone出來。如果只是用Object中預設的clone方法,是淺拷貝的,再次以下面的程式碼驗證:

 1 static class Body implements Cloneable{
 2     public Head head;
 3      
 4     public Body() {}
 5  
 6     public Body(Head head) {this.head = head;}
 7  
 8     @Override
 9     protected Object clone() throws CloneNotSupportedException {
10         return super.clone();
11     }
12      
13 }
14 static class Head /*implements Cloneable*/{
15     public  Face face;
16      
17     public Head() {}
18     public Head(Face face){this.face = face;}
19      
20 } 
21 public static void main(String[] args) throws CloneNotSupportedException {
22      
23     Body body = new Body(new Head());
24      
25     Body body1 = (Body) body.clone();
26      
27     System.out.println("body == body1 : " + (body == body1) );
28      
29     System.out.println("body.head == body1.head : " +  (body.head == body1.head));
30          
32 }

在以上程式碼中, 有兩個主要的類, 分別為Body和Face, 在Body類中, 組合了一個Face物件。當對Body物件進行clone時, 它組合的Face物件只進行淺拷貝。列印結果可以驗證該結論:

  body == body1 : false   body.head == body1.head : true

  如果要使Body物件在clone時進行深拷貝, 那麼就要在Body的clone方法中,將源物件引用的Head物件也clone一份。 

 1 static class Body implements Cloneable{
 2     public Head head;
 3     public Body() {}
 4     public Body(Head head) {this.head = head;}
 5  
 6     @Override
 7     protected Object clone() throws CloneNotSupportedException {
 8         Body newBody =  (Body) super.clone();
 9         newBody.head = (Head) head.clone();
10         return newBody;
11     }
12      
13 }
14 static class Head implements Cloneable{
15     public  Face face;
16      
17     public Head() {}
18     public Head(Face face){this.face = face;}
19     @Override
20     protected Object clone() throws CloneNotSupportedException {
21         return super.clone();
22     }
23 } 
24 public static void main(String[] args) throws CloneNotSupportedException {
25      
26     Body body = new Body(new Head());
27      
28     Body body1 = (Body) body.clone();
29      
30     System.out.println("body == body1 : " + (body == body1) );
31      
32     System.out.println("body.head == body1.head : " +  (body.head == body1.head));
33      
34      
35 }

列印結果為:

  body == body1 : false   body.head == body1.head : true   由此可見, body和body1內的head引用指向了不同的Head物件, 也就是說在clone Body物件的同時, 也拷貝了它所引用的Head物件, 進行了深拷貝

  但是,我們可以看到,Head類中的face物件的clone()是淺複製,所以這裡肯定還會存在一個結果是body.head.face == body1.head.face : false,這表明這樣寫的clone()並不是很徹底,那麼要進行完全徹底的深複製,我們應該寫成這樣:

 1 static class Head implements Cloneable{
 2     public  Face face;
 3      
 4     public Head() {}
 5     public Head(Face face){this.face = face;}
 6     @Override
 7     protected Object clone() throws CloneNotSupportedException {
 8         //return super.clone();
 9         Head newHead = (Head) super.clone();
10         newHead.face = (Face) this.face.clone();
11         return newHead;
12     }
13 } 
14  
15 static class Face implements Cloneable{
16     @Override
17     protected Object clone() throws CloneNotSupportedException {
18         return super.clone();
19     }
20 }

我們需要對物件中的每一個引用進行深拷貝才能徹底實現深拷貝。