java clone詳解.docx_第1頁
java clone詳解.docx_第2頁
java clone詳解.docx_第3頁
java clone詳解.docx_第4頁
java clone詳解.docx_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

Clone使用方法詳解java“指針” Java語言的一個優(yōu)點就是取消了指針的概念,但也導致了許多程序員在編程中常常忽略了對象與引用的區(qū)別,本文會試圖澄清這一概念。并且由于Java不能通過簡單的賦值來解決對象復制的問題,在開發(fā)過程中,也常常要要應用clone()方法來復制對象。本文會讓你了解什么是影子clone與深度clone,認識它們的區(qū)別、優(yōu)點及缺點??吹竭@個標題,是不是有點困惑:Java語言明確說明取消了指針,因為指針往往是在帶來方便的同時也是導致代碼不安全的根源,同時也會使程序的變得非常復雜難以理解,濫用指針寫成的代碼不亞于使用早已臭名昭著的GOTO語句。Java放棄指針的概念絕對是極其明智的。但這只是在Java語言中沒有明確的指針定義,實質(zhì)上每一個new語句返回的都是一個指針的引用,只不過在大多時候Java中不用關(guān)心如何操作這個指針,更不用象在操作C的指針那樣膽戰(zhàn)心驚。唯一要多多關(guān)心的是在給函數(shù)傳遞對象的時候。如下例程:package reference;class ObjString str = init value;public String toString()return str;public class ObjRefObj aObj = new Obj();int aInt = 11;public void changeObj(Obj inObj)inObj.str = changed value;public void changePri(int inInt)inInt = 22;public static void main(String args)ObjRef oRef = new ObjRef();System.out.println(Before call changeObj() method: + oRef.aObj);oRef.changeObj(oRef.aObj);System.out.println(After call changeObj() method: + oRef.aObj);System.out.println(=Print Primtive=);System.out.println(Before call changePri() method: + oRef.aInt);oRef.changePri(oRef.aInt);System.out.println(After call changePri() method: + oRef.aInt);/* RUN RESULTBefore call changeObj() method: init valueAfter call changeObj() method: changed value=Print Primtive=Before call changePri() method: 11After call changePri() method: 11*/這段代碼的主要部分調(diào)用了兩個很相近的方法,changeObj()和changePri()。唯一不同的是它們一個把對象作為輸入?yún)?shù),另一個把Java中的基本類型int作為輸入?yún)?shù)。并且在這兩個函數(shù)體內(nèi)部都對輸入的參數(shù)進行了改動。看似一樣的方法,程序輸出的結(jié)果卻不太一樣。changeObj()方法真正的把輸入的參數(shù)改變了,而changePri()方法對輸入的參數(shù)沒有任何的改變。從這個例子知道Java對對象和基本的數(shù)據(jù)類型的處理是不一樣的。和C語言一樣,當把Java的基本數(shù)據(jù)類型(如int,char,double等)作為入口參數(shù)傳給函數(shù)體的時候,傳入的參數(shù)在函數(shù)體內(nèi)部變成了局部變量,這個局部變量是輸入?yún)?shù)的一個拷貝,所有的函數(shù)體內(nèi)部的操作都是針對這個拷貝的操作,函數(shù)執(zhí)行結(jié)束后,這個局部變量也就完成了它的使命,它影響不到作為輸入?yún)?shù)的變量。這種方式的參數(shù)傳遞被稱為值傳遞。而在Java中用對象的作為入口參數(shù)的傳遞則缺省為引用傳遞,也就是說僅僅傳遞了對象的一個引用,這個引用的概念同C語言中的指針引用是一樣的。當函數(shù)體內(nèi)部對輸入變量改變時,實質(zhì)上就是在對這個對象的直接操作。除了在函數(shù)傳值的時候是引用傳遞,在任何用向?qū)ο笞兞抠x值的時候都是引用傳遞。如:package reference;class PassObjString str = init value;public class ObjPassvaluepublic static void main(String args)PassObj objA = new PassObj();PassObj objB = objA;objA.str = changed in objA;System.out.println(Print objB.str value: + objB.str);/* RUN RESULTPrint objB.str value: changed in objA*/第一句是在內(nèi)存中生成一個新的PassObj對象,然后把這個PassObj的引用賦給變量objA,第二句是把PassObj對象的引用又賦給了變量objB。此時objA和objB是兩個完全一致的變量,以后任何對objA的改變都等同于對objB的改變。即使明白了Java語言中的指針概念也許還會不經(jīng)意間犯下面的錯誤。Hashtable真的能存儲對象嗎?看一看下面的很簡單的代碼,先是聲明了一個Hashtable和StringBuffer對象,然后分四次把StriingBuffer對象放入到Hashtable表中,在每次放入之前都對這個StringBuffer對象append()了一些新的字符串:package reference;import java.util.*;public class HashtableAddpublic static void main(String args)Hashtable ht = new Hashtable();StringBuffer sb = new StringBuffer();sb.append(abc,);ht.put(1,sb);sb.append(def,);ht.put(2,sb);sb.append(mno,);ht.put(3,sb);sb.append(xyz.);ht.put(4,sb);int numObj=0;Enumeration it = ht.elements();while(it.hasMoreElements()System.out.print(get StringBufffer +(+numObj)+ from Hashtable: );System.out.println(it.nextElement();如果你認為輸出的結(jié)果是:get StringBufffer 1 from Hashtable: abc,get StringBufffer 2 from Hashtable: abc,def,get StringBufffer 3 from Hashtable: abc,def,mno,get StringBufffer 4 from Hashtable: abc,def,mno,xyz.那么你就要回過頭再仔細看一看上一個問題了,把對象時作為入口參數(shù)傳給函數(shù),實質(zhì)上是傳遞了對象的引用,向Hashtable傳遞StringBuffer對象也是只傳遞了這個StringBuffer對象的引用!每一次向Hashtable表中put一次StringBuffer,并沒有生成新的StringBuffer對象,只是在Hashtable表中又放入了一個指向同一StringBuffer對象的引用而已。對Hashtable表存儲的任何一個StringBuffer對象(更確切的說應該是對象的引用)的改動,實際上都是對同一個StringBuffer的改動。所以Hashtable并不能真正存儲能對象,而只能存儲對象的引用。也應該知道這條原則對與Hashtable相似的Vector, List, Map, Set等都是一樣的。上面的例程的實際輸出的結(jié)果是:/* RUN RESULTget StringBufffer 1 from Hashtable: abc,def,mno,xyz.get StringBufffer 2 from Hashtable: abc,def,mno,xyz.get StringBufffer 3 from Hashtable: abc,def,mno,xyz.get StringBufffer 4 from Hashtable: abc,def,mno,xyz.*/2.類,對象與引用Java最基本的概念就是類,類包括函數(shù)和變量。如果想要應用類,就要把類生成對象,這個過程被稱作類的實例化。有幾種方法把類實例化成對象,最常用的就是用new操作符。類實例化成對象后,就意味著要在內(nèi)存中占據(jù)一塊空間存放實例。想要對這塊空間操作就要應用到對象的引用。引用在Java語言中的體現(xiàn)就是變量,而變量的類型就是這個引用的對象。雖然在語法上可以在生成一個對象后直接調(diào)用該對象的函數(shù)或變量,如:new String(Hello NDP).substring(0,3)/RETURN RESULT: Hel但由于沒有相應的引用,對這個對象的使用也只能局限這條語句中了。產(chǎn)生:引用總是在把對象作參數(shù)傳遞的過程中自動發(fā)生,不需要人為的產(chǎn)生,也不能人為的控制引用的產(chǎn)生。這個傳遞包括把對象作為函數(shù)的入口參數(shù)的情況,也包括用進行對象賦值的時候。范圍:只有局部的引用,沒有局部的對象。引用在Java語言的體現(xiàn)就是變量,而變量在Java語言中是有范圍的,可以是局部的,也可以是全局的。生存期:程序只能控制引用的生存周期。對象的生存期是由Java控制。用new Object()語句生成一個新的對象,是在計算機的內(nèi)存中聲明一塊區(qū)域存儲對象,只有Java的垃圾收集器才能決定在適當?shù)臅r候回收對象占用的內(nèi)存。沒有辦法阻止對引用的改動。3.java中的clone3.1.什么是clone?在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經(jīng)包含了一些有效值,此時可能會需要一個和A完全相同新對象B,并且此后對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現(xiàn)clone()方法是其中最簡單,也是最高效的手段。Java的所有類都默認繼承java.lang.Object類,在java.lang.Object類中有一個方法clone()。JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。要說明的有兩點:一是拷貝對象返回的是一個新對象,而不是一個引用。二是拷貝對象與用new操作符返回的新對象的區(qū)別就是這個拷貝已經(jīng)包含了一些原來對象的信息,而不是對象的初始信息。3.2.怎樣應用clone()方法?一個很典型的調(diào)用clone()代碼如下:class CloneClass implements Cloneablepublic int aInt;public Object clone()CloneClass o = null;tryo = (CloneClass)super.clone();catch(CloneNotSupportedException e)e.printStackTrace();return o;有三個值得注意的地方,一是希望能實現(xiàn)clone功能的CloneClass類實現(xiàn)了Cloneable接口,這個接口屬于java.lang包,java.lang包已經(jīng)被缺省的導入類中,所以不需要寫成java.lang.Cloneable。另一個值得請注意的是重載了clone()方法。最后在clone()方法中調(diào)用了super.clone(),這也意味著無論clone類的繼承結(jié)構(gòu)是什么樣的,super.clone()直接或間接調(diào)用了java.lang.Object類的clone()方法。下面再詳細的解釋一下這幾點。應該說第三點是最重要的,仔細觀察一下Object類的clone()一個native方法,native方法的效率一般來說都是遠高于java中的非native方法。這也解釋了為什么要用Object中clone()方法而不是先new一個類,然后把原始對象中的信息賦到新對象中,雖然這也實現(xiàn)了clone功能。對于第二點,也要觀察Object類中的clone()還是一個protected屬性的方法。這也意味著如果要應用clone()方法,必須繼承Object類,在Java中所有的類是缺省繼承Object類的,也就不用關(guān)心這點了。然后重載clone()方法。還有一點要考慮的是為了讓其它類能調(diào)用這個clone類的clone()方法,重載之后要把clone()方法的屬性設置為public。、那么clone類為什么還要實現(xiàn)Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其實這個接口僅僅是一個標志,而且這個標志也僅僅是針對Object類中clone()方法的,如果clone類沒有實現(xiàn)Cloneable接口,并調(diào)用了Object的clone()方法(也就是調(diào)用了super.Clone()方法),那么Object的clone()方法就會拋出CloneNotSupportedException異常。以上是clone的最基本的步驟,想要完成一個成功的clone,還要了解什么是影子clone和深度clone。3.3什么是影子clone?下面的例子包含三個類UnCloneA,CloneB,CloneMain。CloneB類包含了一個UnCloneA的實例和一個int類型變量,并且重載clone()方法。CloneMain類初始化UnCloneA類的一個實例b1,然后調(diào)用clone()方法生成了一個b1的拷貝b2。最后考察一下b1和b2的輸出:package clone;class UnCloneA private int i;public UnCloneA(int ii) i = ii; public void doublevalue() i *= 2; public String toString() return Integer.toString(i);class CloneB implements Cloneablepublic int aInt;public UnCloneA unCA = new UnCloneA(111);public Object clone()CloneB o = null;tryo = (CloneB)super.clone();catch(CloneNotSupportedException e)e.printStackTrace();return o;public class CloneMain public static void main(String a)CloneB b1 = new CloneB();b1.aInt = 11;System.out.println(before clone,b1.aInt = + b1.aInt);System.out.println(before clone,b1.unCA = + b1.unCA);CloneB b2 = (CloneB)b1.clone();b2.aInt = 22;b2.unCA.doublevalue();System.out.println(=);System.out.println(after clone,b1.aInt = + b1.aInt);System.out.println(after clone,b1.unCA = + b1.unCA);System.out.println(=);System.out.println(after clone,b2.aInt = + b2.aInt);System.out.println(after clone,b2.unCA = + b2.unCA);/* RUN RESULT:before clone,b1.aInt = 11before clone,b1.unCA = 111=after clone,b1.aInt = 11after clone,b1.unCA = 222=after clone,b2.aInt = 22after clone,b2.unCA = 222*/輸出的結(jié)果說明int類型的變量aInt和UnCloneA的實例對象unCA的clone結(jié)果不一致,int類型是真正的被clone了,因為改變了b2中的aInt變量,對b1的aInt沒有產(chǎn)生影響,也就是說,b2.aInt與b1.aInt已經(jīng)占據(jù)了不同的內(nèi)存空間,b2.aInt是b1.aInt的一個真正拷貝。相反,對b2.unCA的改變同時改變了b1.unCA,很明顯,b2.unCA和b1.unCA是僅僅指向同一個對象的不同引用!從中可以看出,調(diào)用Object類中clone()方法產(chǎn)生的效果是:先在內(nèi)存中開辟一塊和原始對象一樣的空間,然后原樣拷貝原始對象中的內(nèi)容。對基本數(shù)據(jù)類型,這樣的操作是沒有問題的,但對非基本類型變量,我們知道它們保存的僅僅是對象的引用,這也導致clone后的非基本類型變量和原始對象中相應的變量指向的是同一個對象。大多時候,這種clone的結(jié)果往往不是我們所希望的結(jié)果,這種clone也被稱為影子clone。要想讓b2.unCA指向與b2.unCA不同的對象,而且b2.unCA中還要包含b1.unCA中的信息作為初始信息,就要實現(xiàn)深度clone。3.4怎么進行深度clone?把上面的例子改成深度clone很簡單,需要兩個改變:一是讓UnCloneA類也實現(xiàn)和CloneB類一樣的clone功能(實現(xiàn)Cloneable接口,重載clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();程序如下:package clone.ext;class UnCloneA implements Cloneableprivate int i;public UnCloneA(int ii) i = ii; public void doublevalue() i *= 2; public String toString() return Integer.toString(i);public Object clone()UnCloneA o = null;tryo = (UnCloneA)super.clone();catch(CloneNotSupportedException e)e.printStackTrace();return o;class CloneB implements Cloneablepublic int aInt;public UnCloneA unCA = new UnCloneA(111);public Object clone()CloneB o = null;tryo = (CloneB)super.clone();catch(CloneNotSupportedException e)e.printStackTrace();o.unCA = (UnCloneA)unCA.clone();return o;public class CloneMain public static void main(String a)CloneB b1 = new CloneB();b1.aInt = 11;System.out.println(before clone,b1.aInt = + b1.aInt);System.out.println(before clone,b1.unCA = + b1.unCA);CloneB b2 = (CloneB)b1.clone();b2.aInt = 22;b2.unCA.doublevalue();System.out.println(=);System.out.println(after clone,b1.aInt = + b1.aInt);System.out.println(after clone,b1.unCA = + b1.unCA);System.out.println(=);System.out.println(after clone,b2.aInt = + b2.aInt);System.out.println(after clone,b2.unCA = + b2.unCA);/* RUN RESULT:before clone,b1.aInt = 11before clone,b1.unCA = 111=after clone,b1.aInt = 11after clone,b1.unCA = 111=after clone,b2.aInt = 22after clone,b2.unCA = 222*/可以看出,現(xiàn)在b2.unCA的改變對b1.unCA沒有產(chǎn)生影響。此時b1.unCA與b2.unCA指向了兩個不同的UnCloneA實例,而且在CloneB b2 = (CloneB)b1.clone();調(diào)用的那一刻b1和b2擁有相同的值,在這里,b1.i = b2.i = 11。要知道不是所有的類都能實現(xiàn)深度clone的。例如,如果把上面的CloneB類中的UnCloneA類型變量改成StringBuffer類型,看一下JDK API中關(guān)于StringBuffer的說明,StringBuffer沒有重載clone()方法,更為嚴重的是StringBuffer還是一個final類,這也是說我們也不能用繼承的辦法間接實現(xiàn)StringBuffer的clone。如果一個類中包含有StringBuffer類型對象或和StringBuffer相似類的對象,我們有兩種選擇:要么只能實現(xiàn)影子clone,要么就在類的clone()方法中加一句(假設是SringBuffer對象,而且變量名仍是unCA):o.unCA = new StringBuffer(unCA.toString(); /原來的是:o.unCA = (UnCloneA)unCA.clone();還要知道的是除了基本數(shù)據(jù)類型能自動實現(xiàn)深度clone以外,String對象,Integer,Double等是一個例外,它clone后的表現(xiàn)好象也實現(xiàn)了深度clone,雖然這只是一個假象,但卻大大方便了我們的編程。Clone中String和StringBuffer的區(qū)別應該說明的是,這里不是著重說明String和StringBuffer的區(qū)別,但從這個例子里也能看出String類的一些與眾不同的地方。下面的例子中包括兩個類,CloneC類包含一個String類型變量和一個StringBuffer類型變量,并且實現(xiàn)了clone()方法。在StrClone類中聲明了CloneC類型變量c1,然后調(diào)用c1的clone()方法生成c1的拷貝c2,在對c2中的String和StringBuffer類型變量用相應的方法改動之后打印結(jié)果:package clone;class CloneC implements Cloneablepublic String str;public StringBuffer strBuff;public Object clone()CloneC o = null;tryo = (CloneC)super.clone();catch(CloneNotSupportedException e)e.printStackTrace();return o;public class StrClone public static void main(String a)CloneC c1 = new CloneC();c1.str = new String(initializeStr);c1.strBuff = new StringBuffer(initializeStrBuff);System.out.println(before clone,c1.str = + c1.str);System.out.println(before clone,c1.strBuff = + c1.strBuff);CloneC c2 = (CloneC)c1.clone();c2.str = c2.str.substring(0,5);c2.strBuff = c2.strBuff.append( change strBuff clone);System.out.println(=);System.out.println(after clone,c1.str = + c1.str);System.out.println(after clone,c1.strBuff = + c1.strBuff);System.out.println(=);System.out.println(after clone,c2.str = + c2.str);System.out.println(after clone,c2.strBuff = + c2.strBuff);/* RUN RESULTbefore clone,c1.str = initializeStrbefore clone,c1.strBuff = initializeStrBuff=after clone,c1.str = initializeStrafter clone,c1.strBuff = initializeStrBuff change strBuff clone=after clone,c2.str = initiafter clon

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論