1. OverIEw
在實際編程中,我們經常會遇到這樣一個情景:有一個對象A,存在屬性方法,現在需要一個和A完全相同的新對象B,並且B的任何改動都不會影響到A中的值。那麼,最常用的辦法就是對A進行克隆。
2. How to Clone
在Java.lang.Object中有一個clone方法,該方法的簽名如下:
Java代碼
- protected native Object clone() throws CloneNotSupportedException;
該方法返回一個Object實例的拷貝,該實例拷貝具有:
1)拷貝對象是一個新對象而不是一個原對象引用。
2)拷貝的對象中包含的是原有對象信息,而不是對象初始化的信息(也就是說,拷貝過程沒有調用構造函數)。
從第1條我們可以看出,拷貝的對象與原對象存在這樣一種關系:
Java代碼
- 1. x.clone() != x will be true
- 2. x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements
- 3. x.clone().equals(x) will be true, but these are not absolute requirements
要實現克隆,必須滿足一下三點:
1)類必須實現Cloneable接口
2)類必須重載Object類中的clone()方法
3)重載的clone()方法中,必須顯示調用super.clone()。
我們看一下Cloneable接口的定義:
Java代碼
- public interface Cloneable { }
Cloneable接口是不包含任何方法的,僅僅表示一個標志(類似Serializable接口),而且這個標志也是針對Object類中clone()方法的,如果clone類沒有實現Cloneable接口,並調用了Object的clone()方法(也就是調用了super.Clone()方法),那麼Object的clone()方法就會拋出CloneNotSupportedException異常。
按照要求,我們寫一個克隆類的小例子:
Java代碼
- public class DummyClone implements Cloneable{
- public Object clone(){
- DummyClone dc = null;
- try {
- dc = (DummyClone)super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return dc;
- }
- }
3. Shadow Clone
首先,我們來看一個例子:ClassA沒有實現Cloneable接口。ClassB存在ClassA的實例,實現了Cloneable接口,並重載了clone方法。ClassC實例化一個ClassB對象,然後克隆一個對象。
Java代碼
- class ClassA {
- public int a;
- public void doubleA(){
- a = a * 2;
- }
- public ClassA(int aa){
- this.a = aa;
- }
- public String toString(){
- return Integer.toString(a);
- }
- }
- class ClassB implements Cloneable{
- public int b;
- public ClassA ca = new ClassA(11);
- public Object clone(){
- ClassB cb = null;
- try {
- cb = (ClassB)super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return cb;
- }
- }
- public class ClassC {
- public static void main(String[] args) {
- ClassB cb = new ClassB();
- cb.b = 222;
- System.out.println("before clone : cb.b = " + cb.b);
- System.out.println("before clone : cb.ca = " + cb.ca);
- //
- ClassB cb2 = (ClassB)cb.clone();
- cb2.b = 3333;
- cb2.ca.doubleA();
- System.out.println("========================");
- System.out.println("after clone : cb.b = " + cb.b);
- System.out.println("after clone : cb.ca = " + cb.ca);
- System.out.println("========================");
- System.out.println("after clone : cb2.b = " + cb2.b);
- System.out.println("after clone : cb2.ca = " + cb2.ca);
- }
- }
輸出結果如下:
Java代碼
- before clone : cb.b = 222
- before clone : cb.ca = 11
- ========================
- after clone : cb.b = 222
- after clone : cb.ca = 22
- ========================
- after clone : cb2.b = 3333
- after clone : cb2.ca = 22
從結果我們看到,int類型的b被完全的克隆了,而ClassA類型的ca卻沒有被克隆。因為克隆後cb2對b的賦值不會影響原有cb中b的值,而調用cb2.ca.doubleA()方法後,對cb2.ca的改變同時改變了cb.ca,表明cb2.ca與cb.ca僅僅指向同一個對象的不同引用。從中可以看出,調用Object類中clone()方法產生的效果是:先在內存中開辟一塊和原始對象一樣的空間,然後原樣拷貝原始對象中的內容。對基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量clone後的變量和原始對象中相應的變量指向的是同一個對象。
這就是影子克隆。影子克隆,並沒有完整的完成克隆,有時候這並不是我們想要的結果。我們有時需要,就像列子中調用cb2.ca.doubleA()方法時,不會對原對象產生改變,這時候我們就需要deep clone;
4. Deep Clone
要實現深度克隆,在上面的例子基礎上,我們只需要這樣修改即可:
Java代碼
- class ClassA implements Cloneable{
- public int a;
- public void doubleA(){
- a = a * 2;
- }
- public ClassA(int aa){
- this.a = aa;
- }
- public String toString(){
- return Integer.toString(a);
- }
- public Object clone(){
- ClassA ca = null;
- try {
- ca = (ClassA)super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return ca;
- }
- }
- class ClassB implements Cloneable{
- public int b;
- public ClassA ca = new ClassA(11);
- public Object clone(){
- ClassB cb = null;
- try {
- cb = (ClassB)super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- cb.ca = (ClassA)ca.clone();
- return cb;
- }
- }
- public class ClassC {
- public static void main(String[] args) {
- ClassB cb = new ClassB();
- cb.b = 222;
- System.out.println("before clone : cb.b = " + cb.b);
- System.out.println("before clone : cb.ca = " + cb.ca);
- //
- ClassB cb2 = (ClassB)cb.clone();
- cb2.b = 3333;
- cb2.ca.doubleA();
- System.out.println("========================");
- System.out.println("after clone : cb.b = " + cb.b);
- System.out.println("after clone : cb.ca = " + cb.ca);
- System.out.println("========================");
- System.out.println("after clone : cb2.b = " + cb2.b);
- System.out.println("after clone : cb2.ca = " + cb2.ca);
- }
- }
輸出結果:
Java代碼
- before clone : cb.b = 222
- before clone : cb.ca = 11
- ========================
- after clone : cb.b = 222
- after clone : cb.ca = 11
- ========================
- after clone : cb2.b = 3333
- after clone : cb2.ca = 22
基本的數據類型可以自動的實現深度的克隆,然而並不是所有的類都可以進行深度的克隆,就比如String類,其類定義為final,且沒有重載clone方法,我們就無法完成對String類的深度克隆。關於不可變對象的克隆,我們通常沒有必要去克隆。