java對象克隆以及深拷貝和淺拷貝


1.什么是"克隆"?

在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能 會需要一個和A完全相同新對象B,並且此后對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在 Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現clone()方法是其中最簡單,也是最高效的手段。 
Java的所有類都默認繼承java.lang.Object類,在java.lang.Object類中有一個方法clone()。JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。要說明的有兩點:一是拷貝對象返回的是一個新對象,而不是一個引用。二是拷貝對象與用 new操作符返回的新對象的區別就是這個拷貝已經包含了一些原來對象的信息,而不是對象的初始信息。 

2.怎樣應用clone方法

一個很典型的調用clone()代碼如下: 

 1 class CloneClass implements Cloneable{ 
 2  public int aInt; 
 3  public Object clone(){ 
 4   CloneClass o = null; 
 5   try{ 
 6    o = (CloneClass)super.clone(); 
 7   }catch(CloneNotSupportedException e){ 
 8    e.printStackTrace(); 
 9   } 
10   return o; 
11  } 
12

有三個值得注意的地方,一是希望能實現clone功能的CloneClass類實現了Cloneable接口,這個接口屬於java.lang 包,java.lang包已經被缺省的導入類中,所以不需要寫成java.lang.Cloneable。另一個值得請注意的是重載了clone()方 法。最后在clone()方法中調用了super.clone(),這也意味着無論clone類的繼承結構是什么樣的,super.clone()直接或 間接調用了java.lang.Object類的clone()方法。下面再詳細的解釋一下這幾點。 

應該說第三點是最重要的,仔細 觀察一下Object類的clone()一個native方法,native方法的效率一般來說都是遠高於java中的非native方法。這也解釋了為 什么要用Object中clone()方法而不是先new一個類,然后把原始對象中的信息賦到新對象中,雖然這也實現了clone功能。對於第二點,也要 觀察Object類中的clone()還是一個protected屬性的方法。這也意味着如果要應用clone()方法,必須繼承Object類,在 Java中所有的類是缺省繼承Object類的,也就不用關心這點了。然后重寫clone()方法。還有一點要考慮的是為了讓其它類能調用這個clone 類的clone()方法,重寫之后要把clone()方法的屬性設置為public。 

那么clone類為什么還要實現 Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其實這個接口僅僅是一個標志,而且這個標志也僅僅是針對 Object類中clone()方法的,如果clone類沒有實現Cloneable接口,並調用了Object的clone()方法(也就是調用了 super.Clone()方法),那么Object的clone()方法就會拋出CloneNotSupportedException異常。 

3.深拷貝與淺拷貝

淺拷貝是指拷貝對象時僅僅拷貝對象本身(包括對象中的基本變量),而不拷貝對象包含的引用指向的對象。深拷貝不僅拷貝對象本身,而且拷貝對象包含的引用指向的所有對象。舉例來說更加清楚:對象A1中包含對B1的引用,B1中包含對C1的引用。淺拷貝A1得到A2,A2 中依然包含對B1的引用,B1中依然包含對C1的引用。深拷貝則是對淺拷貝的遞歸,深拷貝A1得到A2,A2中包含對B2(B1的copy)的引用,B2 中包含對C2(C1的copy)的引用。

若不對clone()方法進行改寫,則調用此方法得到的對象即為淺拷貝,下面我們着重談一下深拷貝。運行下面的程序,看一看淺拷貝:

 1 class Professor0 implements Cloneable {
 2     String name;
 3     int age;
 4  
 5     Professor0(String name, int age) {
 6         this.name = name;
 7         this.age = age;
 8     }
 9  
10     public Object clone() throws CloneNotSupportedException {
11         return super.clone();
12     }
13 }
 1 class Student0 implements Cloneable {
 2     String name;// 常量對象。
 3     int age;
 4     Professor0 p;// 學生1和學生2的引用值都是一樣的。
 5  
 6     Student0(String name, int age, Professor0 p) {
 7         this.name = name;
 8         this.age = age;
 9         this.p = p;
10     }
11  
12     public Object clone() {
13         Student0 o = null;
14         try {
15             o = (Student0) super.clone();
16         } catch (CloneNotSupportedException e) {
17             System.out.println(e.toString());
18         }
19  
20         return o;
21     }
22 }
 1 public class ShallowCopy {
 2     public static void main(String[] args) {
 3         Professor0 p = new Professor0("wangwu", 50);
 4         Student0 s1 = new Student0("zhangsan", 18, p);
 5         Student0 s2 = (Student0) s1.clone();
 6         s2.p.name = "lisi";
 7         s2.p.age = 30;
 8         s2.name = "z";
 9         s2.age = 45;
10         System.out.println("學生s1的姓名:" + s1.name + "\n學生s1教授的姓名:" + s1.p.name + "," + "\n學生s1教授的年紀" + s1.p.age);// 學生1的教授
11     }
12 }

運行結果:

s2變了,但s1也變了,證明s1的p和s2的p指向的是同一個對象。這在我們有的實際需求中,卻不是這樣,因而我們需要深拷貝:

 1 class Professor implements Cloneable {
 2     String name;
 3     int age;
 4  
 5     Professor(String name, int age) {
 6         this.name = name;
 7         this.age = age;
 8     }
 9  
10     public Object clone() {
11         Object o = null;
12         try {
13             o = super.clone();
14         } catch (CloneNotSupportedException e) {
15             System.out.println(e.toString());
16         }
17         return o;
18     }
19 }
 1 class Student implements Cloneable {
 2     String name;
 3     int age;
 4     Professor p;
 5  
 6     Student(String name, int age, Professor p) {
 7         this.name = name;
 8         this.age = age;
 9         this.p = p;
10     }
11  
12     public Object clone() {
13         Student o = null;
14         try {
15             o = (Student) super.clone();
16         } catch (CloneNotSupportedException e) {
17             System.out.println(e.toString());
18         }
19         o.p = (Professor) p.clone();
20         return o;
21     }
22 }
 1 public class DeepCopy {
 2     public static void main(String args[]) {
 3         long t1 = System.currentTimeMillis();
 4         Professor p = new Professor("wangwu", 50);
 5         Student s1 = new Student("zhangsan", 18, p);
 6         Student s2 = (Student) s1.clone();
 7         s2.p.name = "lisi";
 8         s2.p.age = 30;
 9         System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age);// 學生1的教授不改變。
10         long t2 = System.currentTimeMillis();
11         System.out.println(t2-t1);
12     }
13 }

運行結果:

當然我們還有一種深拷貝方法,就是將對象串行化:

 1 class Professor2 implements Serializable {
 2     /**
 3      * 
 4      */
 5     private static final long serialVersionUID = 1L;
 6     String name;
 7     int age;
 8  
 9     Professor2(String name, int age) {
10         this.name = name;
11         this.age = age;
12     }
 1 class Student2 implements Serializable {
 2     /**
 3      * 
 4      */
 5     private static final long serialVersionUID = 1L;
 6     String name;// 常量對象。
 7     int age;
 8     Professor2 p;// 學生1和學生2的引用值都是一樣的。
 9  
10     Student2(String name, int age, Professor2 p) {
11         this.name = name;
12         this.age = age;
13         this.p = p;
14     }
15  
16     public Object deepClone() throws IOException, OptionalDataException,
17             ClassNotFoundException {
18         // 將對象寫到流里
19         ByteArrayOutputStream bo = new ByteArrayOutputStream();
20         ObjectOutputStream oo = new ObjectOutputStream(bo);
21         oo.writeObject(this);
22         // 從流里讀出來
23         ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
24         ObjectInputStream oi = new ObjectInputStream(bi);
25         return (oi.readObject());
26     }
27  
28 }
 1 public class DeepCopy2 {
 2      
 3     /**
 4      * @param args
 5      */
 6     public static void main(String[] args) throws OptionalDataException,
 7             IOException, ClassNotFoundException {
 8         long t1 = System.currentTimeMillis();
 9         Professor2 p = new Professor2("wangwu", 50);
10         Student2 s1 = new Student2("zhangsan", 18, p);
11         Student2 s2 = (Student2) s1.deepClone();
12         s2.p.name = "lisi";
13         s2.p.age = 30;
14         System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age); // 學生1的教授不改變。
15         long t2 = System.currentTimeMillis();
16         System.out.println(t2-t1);
17     }
18  
19 }

要想序列化對象,必須先創建一個OutputStream,然后把它嵌入ObjectOutputStream。這時就能用writeObject()方法把對象寫入OutputStream。讀的時候需要把InputStream嵌到ObjectInputStream中,然后再調用readObject()方法。不過這樣讀出來的只是一個Object的reference,因此,在用之前,還要下轉型。

對象序列化不僅能保存對象的副本,而且會跟着對象中的reference把它所引用的對象也保存起來,然后再繼續跟蹤那些對象的reference,以此類推。這種情形常被稱作”單個對象所聯結的‘對象網’ “。

但是串行化卻很耗時,在一些框架中,我們便可以感受到,它們往往將對象進行串行化后進行傳遞,耗時較多。

本篇文章來源於兩位前輩的博文,以及《java程序員面試寶典》。為了表示對他人勞動成果的尊重,這里標注出處:

http://www.cnblogs.com/shuaiwhu/archive/2010/12/14/2065088.html
http://www.cnblogs.com/o-andy-o/archive/2012/04/06/2434904.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM