克隆主要分為三類:直接復制復制,淺拷貝,深拷貝。
克隆是基於object類中的clone()方法實現的,下面是clone()的源碼:
protected native Object clone() throws CloneNotSupportedException;
仔細一看,它還是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。
- 第一次聲明保證克隆對象將有單獨的內存地址分配。
- 第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。
- 第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。
因為每個類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因為該方法是protected,所以都不能在類外進行訪問。
要想對一個對象進行復制,就需要對clone方法覆蓋。
直接賦值復制:
直接賦值。在Java中,A a1 = a2,我們需要理解的是這實際上復制的是引用,也就是說a1和a2指向的是同一個對象。因此,當a1變化的時候,a2里面的成員變量也會跟着變化。
為什么需要克隆:
大家先思考一個問題,為什么需要克隆對象?直接new一個對象不行嗎?
答案是:克隆的對象可能包含一些已經修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,所以當需要一個新的對象來保存當前對象的“狀態”就靠clone方法了。那么我把這個對象的臨時屬性一個一個的賦值給我新new的對象不也行嘛?可以是可以,但是一來麻煩不說,二來,大家通過上面的源碼都發現了clone是一個native方法,就是快啊,在底層實現的。
提個醒,我們常見的Object a=new Object();Object b;b=a;這種形式的代碼復制的是引用,即對象在內存中的地址,a和b對象仍然指向了同一個對象。
而通過clone方法賦值的對象跟原來的對象時同時獨立存在的。
如何實現克隆:
在Java語言中,數據類型分為值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等復雜類型。淺克隆和深克隆的主要區別在於是否支持引用類型的成員變量的復制,下面將對兩者進行詳細介紹。
一般步驟是(淺克隆):
1. 被復制的類需要實現Clonenable接口(不實現的話在調用clone方法會拋出CloneNotSupportedException異常), 該接口為標記接口(不含任何方法)
2. 覆蓋clone()方法,訪問修飾符設為public。方法中調用super.clone()方法得到需要的復制對象。(native為本地方法)
class Student implements Cloneable{ private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = (Student)stu1.clone(); System.out.println("學生1:" + stu1.getNumber()); System.out.println("學生2:" + stu2.getNumber()); stu2.setNumber(54321); System.out.println("學生1:" + stu1.getNumber()); System.out.println("學生2:" + stu2.getNumber()); } }
結果:
學生1:12345
學生2:12345
學生1:12345
學生2:54321
上面的復制被稱為淺克隆。
深克隆:
1 package abc; 2 3 class Address implements Cloneable { 4 private String add; 5 6 public String getAdd() { 7 return add; 8 } 9 10 public void setAdd(String add) { 11 this.add = add; 12 } 13 14 @Override 15 public Object clone() { 16 Address addr = null; 17 try{ 18 addr = (Address)super.clone(); 19 }catch(CloneNotSupportedException e) { 20 e.printStackTrace(); 21 } 22 return addr; 23 } 24 } 25 26 class Student implements Cloneable{ 27 private int number; 28 29 private Address addr; 30 31 public Address getAddr() { 32 return addr; 33 } 34 35 public void setAddr(Address addr) { 36 this.addr = addr; 37 } 38 39 public int getNumber() { 40 return number; 41 } 42 43 public void setNumber(int number) { 44 this.number = number; 45 } 46 47 @Override 48 public Object clone() { 49 Student stu = null; 50 try{ 51 stu = (Student)super.clone(); //淺復制 52 }catch(CloneNotSupportedException e) { 53 e.printStackTrace(); 54 } 55 stu.addr = (Address)addr.clone(); //深度復制 56 return stu; 57 } 58 } 59 public class Test { 60 61 public static void main(String args[]) { 62 63 Address addr = new Address(); 64 addr.setAdd("杭州市"); 65 Student stu1 = new Student(); 66 stu1.setNumber(123); 67 stu1.setAddr(addr); 68 69 Student stu2 = (Student)stu1.clone(); 70 71 System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 72 System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 73 74 addr.setAdd("西湖區"); 75 76 System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 77 System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 78 } 79 }
最后我們可以看看API里其中一個實現了clone方法的類:
java.util.Date:
/** * Return a copy of this object. */ public Object clone() { Date d = null; try { d = (Date)super.clone(); if (cdate != null) { d.cdate = (BaseCalendar.Date) cdate.clone(); } } catch (CloneNotSupportedException e) {} // Won't happen return d; }
該類其實也屬於深度復制。
淺克隆和深克隆
1、淺克隆
在淺克隆中,如果原型對象的成員變量是值類型,將復制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。
簡單來說,在淺克隆中,當對象被復制時只復制它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有復制。
在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。
2、深克隆
在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象,深克隆將原型對象的所有引用對象也復制一份給克隆對象。
簡單來說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將復制。
在Java語言中,如果需要實現深克隆,可以通過覆蓋Object類的clone()方法實現,也可以通過序列化(Serialization)等方式來實現。
(如果引用類型里面還包含很多引用類型,或者內層引用類型的類里面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深克隆。)
序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以復制對象本身,而且可以復制其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流里將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。
Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱為標識接口,標識接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實現類是否具有某個功能,如是否支持克隆、是否支持序列化等。 |
解決多層克隆問題
如果引用類型里面還包含很多引用類型,或者內層引用類型的類里面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深克隆。
1 public class Outer implements Serializable{
2 private static final long serialVersionUID = 369285298572941L; //最好是顯式聲明ID
3 public Inner inner;
4 //Discription:[深度復制方法,需要對象及對象所有的對象屬性都實現序列化]
5 public Outer myclone() {
6 Outer outer = null;
7 try {
// 將該對象序列化成流,因為寫在流里的是對象的一個拷貝,而原對象仍然存在於JVM里面。所以利用這個特性可以實現對象的深拷貝
8 ByteArrayOutputStream baos = new ByteArrayOutputStream();
9 ObjectOutputStream oos = new ObjectOutputStream(baos);
10 oos.writeObject(this);
11 // 將流序列化成對象
12 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
13 ObjectInputStream ois = new ObjectInputStream(bais);
14 outer = (Outer) ois.readObject();
15 } catch (IOException e) {
16 e.printStackTrace();
17 } catch (ClassNotFoundException e) {
18 e.printStackTrace();
19 }
20 return outer;
21 }
22 }
Inner也必須實現Serializable,否則無法序列化:
1 public class Inner implements Serializable{ 2 private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID 3 public String name = ""; 4 5 public Inner(String name) { 6 this.name = name; 7 } 8 9 @Override 10 public String toString() { 11 return "Inner的name值為:" + name; 12 } 13 }
這樣也能使兩個對象在內存空間內完全獨立存在,互不影響對方的值。
總結
實現對象克隆有兩種方式:
1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。
注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是優於把問題留到運行時。 |