1. 程式人生 > >java克隆對象clone()的使用方法和作用

java克隆對象clone()的使用方法和作用

變化 什麽事 easy 表復制 deep vra 做什麽 模式 指向

轉自:

997.html">http://www.okrs.cn/blog/news/?997.html

內容摘要

若需改動一個對象,同一時候不想改變調用者的對象。就要制作該對象的一個本地副本。這也是本地副本最常見的一種用途。若決定制作一個本地副本。僅僅需簡單地使用clone()方法就可以。Clone是“克隆”的意思,即制作全然一模一樣的副本。這種方法在基礎類Object中定義成“protected”(受保護)模式。

但在希望克隆的不論什麽衍生類中,必須將其覆蓋為“public”模式。

比如,標準庫類Vector覆蓋了clone(),所以能為Vector調用clone(),

clone
()方法時。通常都有一行代碼 super.clone(); clone 有缺省行為,super.clone();由於首先要把父類中的成員拷貝到位,然後才是復制自己的成員。

java克隆對象

若需改動一個對象,同一時候不想改變調用者的對象。就要制作該對象的一個本地副本。這也是本地副本最常見的一種用途。若決定制作一個本地副本,僅僅需簡單地使用clone()方法就可以。Clone是“克隆”的意思。即制作全然一模一樣的副本。這種方法在基礎類Object中定義成“protected”(受保護)模式。但在希望克隆的不論什麽衍生類中,必須將其覆蓋為“public”模式。比如,標準庫類Vector覆蓋了clone(),所以能為Vector調用clone(),例如以下所看到的:

import java.util.*;

class Int {
  private int i;
  public Int(int ii) { i = ii; }
  public void increment() { i++; }
  public String toString() { 
    return Integer.toString(i); 
  }
}

public class Cloning {
  public static void main(String[] args) {
    Vector v = new Vector();
    for(int i = 0
; i < 10; i++ ) v.addElement(new Int(i)); System.out.println("v: " + v); Vector v2 = (Vector)v.clone(); for(Enumeration e = v2.elements(); e.hasMoreElements(); ) ((Int)e.nextElement()).increment(); System.out.println("v: " + v); } }

clone()方法產生了一個Object,後者必須馬上又一次造型為正確類型。

這個樣例指出Vector的clone()方法不能自己主動嘗試克隆Vector內包括的每一個對象——由於別名問題,老的Vector和克隆的Vector都包括了同樣的對象。我們通常把這樣的情況叫作“簡單復制”或者“淺層復制”,由於它僅僅復制了一個對象的“表面”部分。實際對象除包括這個“表面”以外。還包括句柄指向的全部對象。以及那些對象又指向的其它全部對象。由此類推。

這便是“對象網”或“對象關系網”的由來。若能復制下全部這張網,便叫作“全面復制”或者“深層復制”。
在輸出中可看到淺層復制的結果。註意對v2採取的行動也會影響到v:

v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

一般來說,由於不敢保證Vector裏包括的對象是“可以克隆”(凝視②)的,所以最好不要試圖克隆那些對象。

使類具有克隆能力

雖然克隆方法是在全部類最主要的Object中定義的,但克隆仍然不會在每一個類裏自己主動進行。

這似乎有些不可思議,由於基礎類方法在衍生類裏是肯定能用的。但Java確實有點兒反其道而行之;假設想在一個類裏使用克隆方法,唯一的辦法就是專門加入一些代碼,以便保證克隆的正常進行。

  1. 使用protected時的技巧
    為避免我們創建的每一個類都默認具有克隆能力,clone()方法在基礎類Object裏得到了“保留”(設為protected)。這樣造成的後果就是:對那些簡單地使用一下這個類的客戶程序猿來說,他們不會默認地擁有這種方法;其次,我們不能利用指向基礎類的一個句柄來調用clone()(雖然那樣做在某些情況下特別實用。比方用多形性的方式克隆一系列對象)。在編譯期的時候,這實際是通知我們對象不可克隆的一種方式——並且最奇怪的是。Java庫中的大多數類都不能克隆。因此。假如我們運行下述代碼:
    Integer x = new Integer(l);
    x = x.clone();
    那麽在編譯期,就有一條討厭的錯誤消息彈出,告訴我們不可訪問clone()——由於Integer並沒有覆蓋它,並且它對protected版本號來說是默認的)。
    可是,假若我們是在一個從Object衍生出來的類中(全部類都是從Object衍生的),就有權調用Object.clone(),由於它是“protected”,並且我們在一個繼承器中。基礎類clone()提供了一個實用的功能——它進行的是對衍生類對象的真正“按位”復制。所以相當於標準的克隆行動。然而。我們隨後須要將自己的克隆操作設為public。否則無法訪問。總之,克隆時要註意的兩個關鍵問題是:差點兒肯定要調用super.clone()。以及註意將克隆設為public。
    有時還想在更深層的衍生類中覆蓋clone()。否則就直接使用我們的clone()(如今已成為public),而那並不一定是我們所希望的(然而,由於Object.clone()已制作了實際對象的一個副本。所以也有可能同意這樣的情況)。protected的技巧在這裏僅僅能用一次:首次從一個不具備克隆能力的類繼承,並且想使一個類變成“可以克隆”。

    而在從我們的類繼承的不論什麽場合。clone()方法都是可以使用的,由於Java不可能在衍生之後反而縮小方法的訪問範圍。換言之,一旦對象變得可以克隆,從它衍生的不論什麽東西都是可以克隆的。除非使用特殊的機制(後面討論)令其“關閉”克隆能力。

  2. 實現Cloneable接口
    為使一個對象的克隆能力功成圓滿,還須要做還有一件事情:實現Cloneable接口。這個接口使人稍覺奇怪。由於它是空的!


    interface Cloneable {}
    之所以要實現這個空接口。顯然不是由於我們準備上溯造型成一個Cloneable,以及調用它的某個方法。有些人覺得在這裏使用接口屬於一種“欺騙”行為。由於它使用的特性打的是別的主意,而非原來的意思。Cloneable interface的實現扮演了一個標記的角色,封裝到類的類型中。
    雙方面的原因促成了Cloneable interface的存在。首先,可能有一個上溯造型句柄指向一個基礎類型,並且不知道它是否真的能克隆那個對象。在這樣的情況下,可用instanceof關鍵字(第11章有介紹)調查句柄是否確實同一個能克隆的對象連接:
    if(myHandle instanceof Cloneable) // …
    第二個原因是考慮到我們可能不願全部對象類型都能克隆。

    所以Object.clone()會驗證一個類是否真的是實現了Cloneable接口。

    若答案是否定的。則“擲”出一個CloneNotSupportedException違例。所以在普通情況下,我們必須將“implement Cloneable”作為對克隆能力提供支持的一部分。

java的clone實現

理解了實現clone()方法背後的全部細節後,便可創建出能方便復制的類,以便提供了一個本地副本:

import java.util.*;

class MyObject implements Cloneable {
  int i;
  MyObject(int ii) { i = ii; }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("MyObject can‘t clone");
    }
    return o;
  }
  public String toString() {
    return Integer.toString(i);
  }
}

public class LocalCopy {
  static MyObject g(MyObject v) { 
    v.i++;
    return v;
  }
  static MyObject f(MyObject v) { 
    v.i++;
    return v;
  }
  public static void main(String[] args) {
    MyObject a = new MyObject(11);
    MyObject b = g(a); 
    if(a == b) 
      System.out.println("a == b");
    else 
      System.out.println("a != b");
    System.out.println("a = " + a);
    System.out.println("b = " + b);
    MyObject c = new MyObject(47);
    MyObject d = f(c);
    if(c == d) 
      System.out.println("c == d");
    else 
      System.out.println("c != d");
    System.out.println("c = " + c);
    System.out.println("d = " + d);
  }
} 

無論如何。clone()必須可以訪問,所以必須將其設為public(公共的)。其次,作為clone()的初期行動,應調用clone()的基礎類版本號。

這裏調用的clone()是Object內部預先定義好的。

之所以能調用它,是由於它具有protected(受到保護的)屬性。所以能在衍生的類裏訪問。
Object.clone()會檢查原先的對象有多大,再為新對象騰出足夠多的內存,將全部二進制位從原來的對象拷貝到新對象。這叫作“按位復制”,並且按一般的想法,這個工作應該是由clone()方法來做的。但在Object.clone()正式開始操作前,首先會檢查一個類是否Cloneable,即是否具有克隆能力——換言之,它是否實現了Cloneable接口。若未實現,Object.clone()就擲出一個CloneNotSupportedException違例,指出我們不能克隆它。

因此。我們最好用一個try-catch塊將對super.clone()的調用代碼包圍(或封裝)起來,試圖捕獲一個應當永不出現的違例(由於這裏確實已實現了Cloneable接口)。
在LocalCopy中,兩個方法g()和f()揭示出兩種參數傳遞方法間的差異。當中,g()演示的是按引用傳遞,它會改動外部對象,並返回對那個外部對象的一個引用。

而f()是對自變量進行克隆,所以將其分離出來,並讓原來的對象保持獨立。隨後。它繼續做它希望的事情。甚至能返回指向這個新對象的一個句柄,並且不會對原來的對象產生不論什麽副作用。註意以下這個多少有些古怪的語句:
v = (MyObject)v.clone();
它的作用正是創建一個本地副本。

為避免被這樣的一個語句搞混淆。記住這樣的相當奇怪的編碼形式在Java中是全然同意的,由於有一個名字的全部東西實際都是一個句柄。所以句柄v用於克隆一個它所指向的副本。並且終於返回指向基礎類型Object的一個句柄(由於它在Object.clone()中是那樣被定義的)。隨後必須將其造型為正確的類型。
在main()中,兩種不同參數傳遞方式的差別在於它們分別測試了一個不同的方法。輸出結果例如以下:

a == b
a = 12
b = 12
c != d
c = 47
d =48

大家要記住這樣一個事實:Java對“是否等價”的測試並不正確所比較對象的內部進行檢查,從而核實它們的值是否同樣。

==和!=運算符僅僅是簡單地對照句柄的內容。若句柄內的地址同樣,就覺得句柄指向同樣的對象,所以覺得它們是“等價”的。所以運算符真正檢測的是“由於別名問題,句柄是否指向同一個對象?”

java Object.clone()的效果

調用Object.clone()時,實際發生的是什麽事情呢?當我們在自己的類裏覆蓋clone()時。什麽東西對於super.clone()來說是最關鍵的呢?根類中的clone()方法負責建立正確的存儲容量。並通過“按位復制”將二進制位從原始對象中拷貝到新對象的存儲空間。也就是說,它並不僅僅是預留存儲空間以及復制一個對象——實際須要調查出欲復制之對象的準確大小,然後復制那個對象。由於全部這些工作都是在由根類定義之clone()方法的內部代碼中進行的(根類並不知道要從自己這裏繼承出去什麽),所以大家也許已經猜到,這個過程須要用RTTI推斷欲克隆的對象的實際大小。採取這樣的方式。clone()方法便可建立起正確數量的存儲空間。並對那個類型進行正確的按位復制。


無論我們要做什麽。克隆過程的第一個部分通常都應該是調用super.clone()。通過進行一次準確的復制。這樣做可為興許的克隆進程建立起一個良好的基礎。隨後,可採取還有一些必要的操作。以完畢終於的克隆。


為確切了解其它操作是什麽,首先要正確理解Object.clone()為我們帶來了什麽。

特別地。它會自己主動克隆全部句柄指向的目標嗎?以下這個樣例可完畢這樣的形式的檢測:

public class Snake implements Cloneable {
  private Snake next;
  private char c; 
  Snake(int i, char x) {
    c = x;
    if(--i > 0)
      next = new Snake(i, (char)(x + 1));
  }
  void increment() {
    c++;
    if(next != null)
      next.increment();
  }
  public String toString() {
    String s = ":" + c;
    if(next != null)
      s += next.toString();
    return s;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {}
    return o;
  }
  public static void main(String[] args) {
    Snake s = new Snake(5, ‘a‘);
    System.out.println("s = " + s);
    Snake s2 = (Snake)s.clone();
    System.out.println("s2 = " + s2);
    s.increment();
    System.out.println(
      "after s.increment, s2 = " + s2);
  }
} 

一條Snake(蛇)由數段構成,每一段的類型都是Snake。

所以。這是一個一段段鏈接起來的列表。全部段都是以循環方式創建的,每做好一段。都會使第一個構建器參數的值遞減,直至終於為零。而為給每段賦予一個獨一無二的標記,第二個參數(一個Char)的值在每次循環構建器調用時都會遞增。


increment()方法的作用是循環遞增每一個標記,使我們能看到發生的變化;而toString則循環打印出每一個標記。

輸出例如以下:

s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f

這意味著僅僅有第一段才是由Object.clone()復制的。所以此時進行的是一種“淺層復制”。若希望復制整條蛇——即進行“深層復制”——必須在被覆蓋的clone()裏採取附加的操作。


通常可在從一個能克隆的類裏調用super.clone()。以確保全部基礎類行動(包括Object.clone())可以進行。隨著是為對象內每一個句柄都明白調用一個clone()。否則那些句柄會別名變成原始對象的句柄。構建器的調用也大致同樣——首先構造基礎類。然後是下一個衍生的構建器……以此類推,直到位於最深層的衍生構建器。差別在於clone()並不是個構建器,所以沒有辦法實現自己主動克隆。為了克隆。必須由自己明白進行。

克隆合成對象

試圖深層復制合成對象時會遇到一個問題。

必須假定成員對象中的clone()方法也能依次對自己的句柄進行深層復制,以此類推。

這使我們的操作變得復雜。

為了能正常實現深層復制,必須對全部類中的代碼進行控制。或者至少全面掌握深層復制中須要涉及的類,確保它們自己的深層復制能正確進行。


以下這個樣例總結了面對一個合成對象進行深層復制時須要做哪些事情:

class DepthReading implements Cloneable {
  private double depth;
  public DepthReading(double depth) { 
    this.depth = depth;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}

class TemperatureReading implements Cloneable {
  private long time;
  private double temperature;
  public TemperatureReading(double temperature) {
    time = System.currentTimeMillis();
    this.temperature = temperature;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}

class OceanReading implements Cloneable {
  private DepthReading depth;
  private TemperatureReading temperature;
  public OceanReading(double tdata, double ddata){
    temperature = new TemperatureReading(tdata);
    depth = new DepthReading(ddata);
  }
  public Object clone() {
    OceanReading o = null;
    try {
      o = (OceanReading)super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    o.depth = (DepthReading)o.depth.clone();
    o.temperature = 
      (TemperatureReading)o.temperature.clone();
    return o; 
  }
}

public class DeepCopy {
  public static void main(String[] args) {
    OceanReading reading = 
      new OceanReading(33.9, 100.5);
    OceanReading r = 
      (OceanReading)reading.clone();
  }
}  

DepthReading和TemperatureReading很類似;它們都僅僅包括了基本數據類型。

所以clone()方法可以很easy:調用super.clone()並返回結果就可以。註意兩個類使用的clone()代碼是全然一致的。
OceanReading是由DepthReading和TemperatureReading對象合並而成的。為了對其進行深層復制。clone()必須同一時候克隆OceanReading內的句柄。為達到這個目標。super.clone()的結果必須造型成一個OceanReading對象(以便訪問depth和temperature句柄)。

java.lang.Object裏面有一個方法clone()

protected Object clone()
throws CloneNotSupportedException創建並返回此對象的一個副本。

“副本”的準確含義可能依賴於對象的類。這樣做的目的是,對於不論什麽對象 x,表達式:
x.clone() != x為 true,表達式:
x.clone().getClass() == x.getClass()也為 true。但這些並不是必須要滿足的要求。

普通情況下:
x.clone().equals(x)為 true。但這並不是必須要滿足的要求。


依照慣例,返回的對象應該通過調用 super.clone 獲得。假設一個類及其全部的超類(Object 除外)都遵守此約定。則 x.clone().getClass() == x.getClass()。

依照慣例。此方法返回的對象應該獨立於該對象(正被復制的對象)。要獲得此獨立性,在 super.clone 返回對象之前,有必要對該對象的一個或多個字段進行改動。這通常意味著要復制包括正在被復制對象的內部“深層結構”的全部可變對象,並使用對副本的引用替換對這些對象的引用。假設一個類僅僅包括基本字段或對不變對象的引用,那麽通常不須要改動 super.clone 返回的對象中的字段。

Object 類的 clone 方法運行特定的復制操作。

首先,假設此對象的類不能實現接口 Cloneable,則會拋出 CloneNotSupportedException。註意。全部的數組都被視為實現接口 Cloneable。否則,此方法會創建此對象的類的一個新實例,並像通過分配那樣,嚴格使用此對象對應字段的內容初始化該對象的全部字段;這些字段的內容沒有被自我復制。所以。此方法運行的是該對象的“淺表復制”,而不“深層復制”操作。

Object 類本身不實現接口 Cloneable,所以在類為 Object 的對象上調用 clone 方法將會導致在運行時拋出異常。

返回:
此實例的一個副本。


拋出:
CloneNotSupportedException - 假設對象的類不支持 Cloneable 接口。則重寫 clone 方法的子類也會拋出此異常。以指示無法復制某個實例。

事實上clone()被定義為protected是有原因的,由於有些時候某些類實例我們不希望能被復制.那我們怎麽用這種方法呢?僅僅要使用一個super關鍵字就夠了.以下樣例可以解釋清楚clone()的作用與其使用方法:
public class C 
 {
     static class Strings implements Cloneable
     {
         private String str;

         public void SetStr(String s){
             this.str = s;
        }

        public String GetStr(){
            return this.str;
        }

        public Object clone()throws CloneNotSupportedException{
            return super.clone();
        }
    }

    public static void main(String[] args) 
    {
        try{
            Strings str1 = new Strings();
            str1.SetStr("Hello World!");

            System.out.println("-----------------");

            System.out.println("str1.SetStr(\"Hello World!\");");
            System.out.println("str2 = (Strings)str1.clone();");
            Strings str2 = (Strings)str1.clone();
            System.out.println("str1:"+str1.GetStr());
            System.out.println("str2:"+str2.GetStr());
            System.out.print("print object str1:");
            System.out.println(str1);
            System.out.print("print object str2:");
            System.out.println(str2);

            System.out.println("-----------------");
            System.out.println("str2.setStr(\"Hello!\");");
            str2.SetStr("Hello!");
            System.out.println("str1:"+str1.GetStr());
            System.out.println("str2:"+str2.GetStr());
            System.out.print("print object str1:");
            System.out.println(str1);
            System.out.print("print object str2:");
            System.out.println(str2);

            System.out.println("-----------------");

            System.out.println("str1 = str2;");
            str1 = str2;
            System.out.print("print object str1:");
            System.out.println(str1);
            System.out.print("print object str2:");
            System.out.println(str2);

            System.out.println("-----------------");
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
}

運行結果例如以下:

-----------------
str1.SetStr("Hello World!");
str2 = (Strings)str1.clone();
str1:Hello World!
str2:Hello World!
print object str1:[email protected]
print object str2:[email protected]
-----------------
str2.setStr("Hello!");
str1:Hello World!
str2:Hello!
print object str1:[email protected]
print object str2:[email protected]
-----------------
str1 = str2;
print object str1:[email protected]
print object str2:[email protected]
-----------------

java克隆對象clone()的使用方法和作用