java clone

原文地址 http://hi.baidu.com/limp_t/blog/item/83bdb2ad2025f60d4a36d67d.html
經常聽到有人說java中沒有指針。事實如此嗎?no,java是有指針的,只不過換了個名字而已,也就是我們經常提到的引用。我們知道,在java中一切都是對象,那麼我們如何操控對象?如何在成千上萬的對象中找到我們所需的那個對象呢?又是如何讓對象按照我們的意思來完成任務的呢?

  Object o = new Object();

   這是java中最常見的語句了,在這句話中做了三件事。首先聲明一個Object類型的變量o,在內存中爲對象劃分一塊地址new Object(),將聲明的變量指向內存中的對象。如此一來,我們就可以通過o來操縱對象了。就好像孩子們玩的遙控飛機,在空中飛行的是飛機,而使它做出 優美動作的卻是孩子們手中的搖控器。

  "克隆"是如今聽到的較多的詞彙,聽說已經將某隻羊克隆了好幾份了。但願這種技術不要在人身上實驗。java中也有"克隆",與現實世界的克隆一樣,將一個實際存在的對象拷貝幾份。如下:

 

 

//倒黴的羊
public class Sheep implements Cloneable{
    
private String name;
    
public void setName(String arg) 
        name 
= arg;
    }

    
public String getName(){
            
return name;
    }

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

}



//克隆
public class Main {
    
public static void main(String[] args) throws CloneNotSupportedException{
        Sheep sheep 
= new Sheep(); //先得到那隻羊的實例
        sheep.setName("我是真的"); //給它做個記號
        System.out.println("sheep.getName() = " + sheep.getName());
        Sheep sheepClone 
= (Sheep)sheep.clone(); //開始克隆
        System.out.println("sheepClone.getName() = " + sheepClone.getName());
    }

}

  運行程序結果爲:

  sheep.getName() = 我是真的

  sheepClone.getName() = 我是真的

  兩隻羊是一模一樣的(哪怕那隻羊瘸腿)。讓我們來看看代碼。首先要注意的是Sheep類實現了Cloneable接口(該接口屬於java.lang包, 默認已經導入了),該接口中並沒有定義要實現的方法,是個空接口,起標誌作用。也就是說,實現了這個接口的羊就不再是隻普通的羊,它是一只可以被克隆的 羊。再往下看,有個clone方法,返回Object類型的對象,並拋出CloneNotSupportedException異常。該方法覆寫了父類 (Object)的clone方法,並在最後調用了super.clone(),這也意味着無論clone類繼承結構是什麼樣的,super.clone ()都會直接或間接調用Object類的clone()方法。看看jdk幫助文檔會發現,Object類的clone()是一個native方法,我們知 道,native方法的效率一般來說都是遠高於java中的非native方法。這也說明了new一個對象,然後將原對象中的數據導入到新創建的對象中去 的做法是多麼愚蠢。必須說明的是Object中的clone方法是protected的,所以要使用clone就必須繼承Object類(默認)。並且爲 了可以使其它類調用該方法,必須將其作用域設置爲public.

  以上只是一個簡單clone的實現。明天說說"影子clone"和"深度clone".

  夜,深了。何爲影子clone?先看一下例子。

   在

//倒黴的羊
public class Sheep implements Cloneable{
    
private String name;
    
public void setName(String arg) 
        name 
= arg;
    }

    
public String getName() {
        
return name;
    }

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

}

//羊圈
public class Sheepfold implements Cloneable {
    
public Sheep sheep;
    
public String name;
    
public Sheepfold() {
        sheep 
= new Sheep();
    }

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

}

//克隆
public class Main {
    
public static void main(String[] args) throws Exception {
        Sheepfold fold 
= new Sheepfold();
        fold.name 
= "小羊圈";
        fold.sheep.setName(
"小羊");
        Sheepfold fold2 
= (Sheepfold)fold.clone();
        System.out.println(
" fold2.name = " + fold2.name);
        System.out.println(
" fold2.sheep.getName() = " + fold2.sheep.getName());
        fold2.name 
= "大羊圈";
        fold2.sheep.setName(
"大羊");
        System.out.println(
"=====================================");
        System.out.println(
" fold2.name = " + fold2.name);
        System.out.println(
"* fold2.sheep.getName() = " + fold2.sheep.getName());
        System.out.println(
" fold.name = " + fold.name);
        System.out.println(
"* fold.sheep.getName() = " + fold.sheep.getName());
        System.out.println(
"====================================="); 
    }

}

這個例子中有三個類,Sheep和Sheepflod都實現了Cloneable接口,並且覆寫了Object類的clone方法,說明這兩個類是具 有克隆能力的。注意一點,在Sheepflod中持有一個Sheep的實例,並在Main類中對其進行克隆,結果如下:

fold2.name = 小羊圈
fold2.sheep.getName() = 小羊
=====================================
fold2.name = 大羊圈
* fold2.sheep.getName() = 大羊
fold.name = 小羊圈
* fold.sheep.getName() = 大羊
=====================================

請注意一下結果中帶有"*"號的兩條結果語句。fold2.sheep和fold.sheep的name都變爲了"大羊 ",很奇怪是嗎?在此之前,我們只對fold2.sheep的name賦過值。爲什麼fold.sheep的name也變爲了"大羊"呢?原因很簡單,因 爲它們是指向同一個對象的不同引用。從中可以看出,調用Object類中clone()方法時,首先在內存中劃分一塊同原對象相同的空間,然後將原對象的 內容原樣拷貝至新對象。我們知道,java中有基本數據類型,對於基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量,它們保存的僅僅是對象的引 用,這也是爲什麼clone後非基本類型變量和原對象中的變量指向同一個對象的原因。可能你已經注意到,程序中用到了String類型,即對象,爲什麼沒 有出現引用指向同一地址的情況?這是因爲String是一個不可更改的類(immutable class),每次給它賦值時,都會產生一個新的String對象。如 String str = "a"; str += "b";在這兩句代碼中,當執行str += "b"時,實際上是重新成生了一個值爲"ab"的 String對象,即重新分配了一塊內存空間。以上clone方法通常被稱爲"影子clone"."影子clone"給我們留下了一個問題,即多個引用指 向同一個對象。如何解決該問題呢?答案爲"深度clone".把上面的例子改成深度clone很簡單,只需將Sheepfold的clone()方法改爲 如下即可:很簡單,只需將Sheepfold的clone()方法改爲如下即可:
(R q9N Y&W S#T0

public Object clone() throws CloneNotSupportedException {
Sheepfold fold = (Sheepfold)super.clone();
sheep = (Sheep)fold.sheep.clone();
return fold;
}

Clone基本知識儲備

在Java裏提到clone技術,就不能不提 java.lang.Cloneable接口和含有clone方法的Object類。所有具有clone功能的類都有一個特性,那就是它直接或間接地實現 了Cloneable接口。否則,我們在嘗試調用clone()方法時,將會觸發CloneNotSupportedException異常。下面我們通 過對Object類的部分源碼的分析,來發現和理解這一特性。請看JDK中Object# clone()方法的源碼:

/* 
…………
* @return a clone of this instance.
* @exception? CloneNotSupportedException? if the object''s class does not
*support the Cloneable interface. Subclasses
*that override the clone method can also
* throw this exception to indicate that an instance cannot
*be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;

這段源碼的@exception部分的描述內容證實了上文關於clone對象特性論斷的正確 性。它明確指出對象類必須支持Cloneable接口,否則即使派生類覆蓋了Object#clone()方法,也同樣會拋出 CloneNotSupportedException這個異常。關於覆蓋clone()方法,後續文章將會用專門篇幅進行比較詳細的分析.

一、clone的實現

1.實現Cloneable接口

通過上一篇的介紹,我們知道,一個類若要具備clone功能,就必須實現 Cloneable接口。做到這一步,clone功能已經基本實現了。Clone功能對我們來說,最主要的還是要能夠使用它。那麼我們如何才能使用 clone功能呢?答案是覆蓋Object#clone()方法。

2. 覆蓋Object#clone()方法

爲什麼需要覆蓋Object#clone()方法?這裏得再次從jdk源碼說起。JDK中Object# clone()方法的原型是:

protected native Object clone() throws CloneNotSupportedException;

是否注意到,這裏clone()方法修飾符是protected,而不是 public。這種訪問的不可見性使得我們對Object#clone()方法不可見。相信讀者已明白爲什麼要覆蓋Object#clone()方法。而 且,覆蓋的方法的修飾符必須是public,如果還保留爲protected,覆蓋將變得沒有實際意義。下面舉一個具有clone功能的簡單的例子:

/* 

* 具有clone功能的類的例子

*/

public class CloneableObjExample implements Cloneable {

//……部分代碼已省略……

private String name = null;

private int score = 0;

/**

* NOTE: 將protected 修飾符 更改爲 public

* @see java.lang.Object#clone()

*/

public/*protected*/ Object clone() throws CloneNotSupportedException {

// call父類的clone方法

Object result = super.clone();

//TODO: 定製clone數據

return result;

}

}

3.定製clone

至此,clone已經真相大白。Clone的對象我們可以對其進行定製。還就上面的例子來說。下面的方法對功能做了一定的增強:

public/*protected*/ Object clone() throws CloneNotSupportedException { 

// call父類的clone方法

CloneableObjExample result = (CloneableObjExample)super.clone();



//TODO: 定製clone數據

//雖然”clone”了,但還可以做點調整

result.name = “New Name”;

result.score = 90;

return result;

}

Clone通常有兩種類型即淺clone和深clone。首先,分析一下兩種的不同。淺 clone和深clone都是clone,它們本質區別是對象內部的成員屬性(非原生類型屬性,如int等)在clone時是否處理爲引用。如果仍然保留 爲引用,則稱爲淺clone,反之稱爲深clone。其實這兩個概念也是相對的概念。在處理上它們有點區別,淺clone方式得到clone對象即可,深 clone方式在得到clone對象後,還需要對引用的成員屬性進行“clone”處理。從這個層次上說,深clone並沒有什麼特別地困難,簡單講就是 創建好對象,再設置一些成員屬性。關於深clone,網上的文章已經有太多,有點目不暇接的感覺,本文不再贅述,這也不是本文的重點。

實現方案爲採用java reflection技術和遞歸相結合。

大致步驟描述如下:首先採用java reflection技術動態獲取成員方法列表。然後視clone的深度,對具備clone條件的並且有必要clone的成員進行逐一clone。這是一 個遞歸的過程,直到clolne深度已到爲止或者到對象已經沒有需要clone的成員屬性爲止。

何爲具備clone條件的並且有必要clone的成員進行逐一clone?比 如,原生類型(primitive type),定爲瞬態(Transient)的類型,不可訪問的類型(!Field#isAccessible()),沒實現Cloneable接口的類 型等等,都不具備clone條件。String等java定義的類型就不需要再深入clone了,這些屬於沒必要進行clone的情況。但List類型等 “容器”類是有必要clone的成員類型。

據此,遞歸程序示意如下(deepClone爲java 方法):

/** 

* @return Object 返回clone的對象

* @param obj 原對象

* @param length clone的深度

*/

public Object deepClone(Object obj, int length) {

Object result = obj;

//此處爲僞代碼: 如果對象obj不具備clone條件,就返回result,這也是遞歸的結束條件。

//此處爲僞代碼: 如果對象obj沒必要clone,就返回result

/**
*此處爲僞代碼:開始進行“clone”對象。這地方是調一個抽象方法來處理,
*這樣可以增加很多靈活性。該方法主要目的是實現“clone”對象方案。注意:
*這裏面的“clone”方案可能是我們想都想不到的方案,它可能有很多創意,
*但效果都是一樣的,就是要“clone”個新的對象出來。當然最容易想的就
*是Object#clone()方法了。示意如下:
*/
result = om.clone(obj);

/**
*此處爲僞代碼: 獲取具備clone條件的並且有必要clone的所有成員。這地方
*也是調一個抽象方法來處理。同樣是爲了增強靈活性。獲取這些成員的方法
*有很多,可能是通過setter和getter對來得到,也可能是通過get fields
*等等方法得到(這種方法可能不少成員是無法直接訪問的,往往需要結合別
*的方法),甚至是多種方法的綜合。總之,目的只有一個,就是獲得這些成員。
*/

for (int i = 0; i < fields.length; i++) {

//對成員進行處理

//如果已不需要再判斷成員了,那除了“容器”成員外,已經clone結束

if (length <= 1) {

if (!“容器”成員) {

continue;

}

try {

//只需clone一次了,注意遞歸方法的深度參數傳入1

clonedFieldValue = deepClone(“容器”成員的值, 1);

} catch (Exception ex2) {

ex2.printStackTrace();

return result;

}

} else {

try {

clonedFieldValue = deepClone(成員的值, length - 1);

} catch (Exception ex) {

ex.printStackTrace();

return result;

}

}

try {

//此處爲僞代碼:將clone好的值(clonedFieldValue)設進去

} catch (Exception ex) {

ex.printStackTrace();

return result;

}

}//for..

return result;

}

至此,已完成了“N深clone”。下面討論一下別的相關問題。比如說這種深度clone原 本是A-->B-->C--……-->xz這樣一種情況,就是說A類含有B成員,B裏面又含有C成員,依此類推。如果想在“N深 clone”時,只clone“xz”這個成員怎麼辦?其實很簡單,這個問題主要是要解決在遞歸過程中有些成員需要clone同時有些成員不需clone 仍保留引用這個問題。在上面的遞歸示例中已經提到,實現“clone”的“方案”已經被定義成抽象方法,那麼我們只要對這個方法做一個滿足這個需求的實現 即可。


 
發佈了26 篇原創文章 · 獲贊 4 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章