這篇東西我寫(xiě)了好長(cháng)時(shí)間,因為我把Thinking in Java, 3rd Ed中有關(guān)內容仔細翻了個(gè)遍。E文版的看起來(lái)速度雖然慢,卻很引人思考。
Java平臺中有些類(lèi)被稱(chēng)為Immutable Class,因為這些類(lèi)中的方法不能改變當前對象的值。
舉個(gè)例子,java.lang.String是個(gè)Immutable Class,如果我們這樣寫(xiě):
String str = "ZephyrFalcon";
str.substring(0, 6);
那么str的內容是什么呢?沒(méi)錯,還是"ZephyrFalcon"。str.substring(0, 6)返回的確實(shí)是"Zephyr",但是它不能改變str指向的對象的的內容,只不過(guò)返回一個(gè)新的String對象罷了,內容是"Zephyr"。于是,單獨執行String.substring()方法沒(méi)有效果,是沒(méi)有什么意義的,前面還要有個(gè)String reference引用它才行。
類(lèi)似的,所有基本類(lèi)型的Wrapper Class也全部是Immutable Class。
Immutable Class的特殊性質(zhì)會(huì )在我們進(jìn)行Object Cloning的時(shí)候發(fā)揮特殊效果,呵呵。這我們最后再說(shuō)。
Object Cloning的概念很簡(jiǎn)單:復制一個(gè)對象。注意,是對象,也就是實(shí)例而不是類(lèi)。All argument passing in Java is performed by passing references(Thinking in Java, 3rd Ed),除了primitives。另外,只有reference有作用域問(wèn)題,object沒(méi)有。當我們需要保持原有對象,希望創(chuàng )建它的一個(gè)副本的時(shí)候(比如創(chuàng )建一個(gè)對象的local copy),cloning就派上用場(chǎng)了。
java.lang.Object類(lèi)具有clone()這個(gè)方法,并且是protected權限(public > protected > default > private)。于是所有的Java類(lèi)全都有clone()這個(gè)方法,默認情況是protected。這個(gè)protected權限在這里很tricky,它意味著(zhù)兩件事:
1.默認情況下程序員無(wú)法對某個(gè)類(lèi)簡(jiǎn)單的使用clone(),這是由于權限問(wèn)題不能access。(簡(jiǎn)單使用指并非繼承這個(gè)類(lèi)。)
2.我們無(wú)法通過(guò)對一個(gè)類(lèi)的base class的reference來(lái)調用這個(gè)類(lèi)的clone()。雖然有時(shí)候這很有用。比如當我們想要多態(tài)的(polymorphically)克隆一大批對象的時(shí)候。即這些對象是不同的類(lèi)型,但是都是Object。我們可以用Object引用去refer它們,但是無(wú)法通過(guò)這些引用的clone()來(lái)復制這些對象。哦,丟人了...實(shí)際上這還是因為權限access問(wèn)題...和上一個(gè)一樣,我的我的。
當然啦,如果你在一個(gè)沒(méi)有override clone()方法的類(lèi)里調用這個(gè)類(lèi)本身的clone(),也就是繼承的那個(gè),當然是可以的。Object.clone()的行為非常神奇,它會(huì )對derived class object進(jìn)行bitwise duplication,于是我們明明調用的是base class的clone()卻能復制出derived class object。
看來(lái)Java里的克隆比Heroes III里面的克隆魔法復雜多了!
結論就很自然了,如果我們不override clone()方法,并把其權限變成public的話(huà),其他的類(lèi)就無(wú)法使用它了(不明白自己翻Tutorial/Nutshell去)。當然了,在這個(gè)我們自己實(shí)現的clone()里,要調用super.clone()。
一旦某個(gè)類(lèi)用上面提到的方法override了clone()方法,它的子類(lèi)將自動(dòng)擁有相應的clone自己的能力。如下:
You’ll probably want to override clone() in any further derived classes; otherwise, your (now public) clone() will be used, and that might not do the right thing (although, since Object.clone() makes a copy of the actual object, it might). The protected trick works only once: the first time you inherit from a class that has no cloneability and you want to make a class that’s cloneable. In any classes inherited from your class, the clone() method is available since it’s not possible in Java to reduce the access of a method during derivation. That is, once a class is cloneable, everything derived from it is cloneable unless you use provided mechanisms (described later) to “turn off” cloning.
另外,java.lang.Cloneable提供了克隆能力的標記接口。這個(gè)接口沒(méi)有任何方法,對,和java.io.Serializable一樣,它僅僅是個(gè)標記接口(tagging interface),標志著(zhù)此類(lèi)可以克隆。如果我們調用一個(gè)類(lèi)的clone()方法,而這個(gè)類(lèi)沒(méi)有實(shí)現此接口,則會(huì )在運行時(shí)拋出java.lang.CloneNotSupportedException異常。
First of all, for clone( ) to be accessible, you must make it public. Second, for the initial part of your clone( ) operation, you should call the base-class version of clone( ). The clone( ) that’s being called here is the one that’s predefined inside Object, and you can call it because it’s protected and thereby accessible in derived classes.
Object.clone( ) figures out how big the object is, creates enough memory for a new one, and copies all the bits from the old to the new. This is called a bitwise copy, and is typically what you’d expect a clone( ) method to do. But before Object.clone( ) performs its operations, it first checks to see if a class is Cloneable—that is, whether it implements the Cloneable interface. If it doesn’t, Object.clone( ) throws a CloneNotSupportedException to indicate that you can’t clone it. Thus, you’ve got to surround your call to super.clone( ) with a try block to catch an exception that should never happen (because you’ve implemented the Cloneable interface).
Whatever you do, the first part of the cloning process should normally be a call to super.clone( ). This establishes the groundwork for the cloning operation by making an exact duplicate. At this point you can perform other operations necessary to complete the cloning.
關(guān)于shadow copy和deep copy:
如果某對象的某成員域是引用,那么此對象被clone以后該引用也被clone,仍然指向原來(lái)它所指的對象。這稱(chēng)為shadow copy。舉個(gè)最簡(jiǎn)單的例子就是,一個(gè)容器/集合類(lèi),例如java.util.Hashtable被clone以后,它里面存放的引用仍然指向原來(lái)的對象。當原來(lái)的Hashtable中的對象改變時(shí),新的Hashtable副本中的對象也會(huì )改變,原因是它們實(shí)際上保存的對象不是local copy,而是引用。
如果我們希望一個(gè)對象被clone后,它的成員域引用原來(lái)對象新的副本,我們只能在此對象的clone()方法里加入語(yǔ)句來(lái)實(shí)現。具體來(lái)說(shuō)就是,對于希望引用新副本的成員域,把它原來(lái)引用的對象克隆,再讓這個(gè)成員域引用這個(gè)新副本。這稱(chēng)為deep copy。遺憾的是deep copy并不總能成功,因為很多對象是不能克隆的。比如Java標準庫里面大多數類(lèi)都沒(méi)有實(shí)現Cloneable接口。這是由于歷史原因。Java語(yǔ)言發(fā)明之初被用在機頂盒等設備上,而不是Internet上。這時(shí)候讓所有的對象都具有Clone能力是很合理的,于是clone()方法被放在java.lang.Object里,而且是public的。后來(lái)Java在Internet上流行起來(lái),安全性的重要日益凸現。很多安全相關(guān)的對象不能被隨意復制。于是clone()變成了protected,還多了Cloneable接口,使類(lèi)在實(shí)現的時(shí)候就能決定是否要擁有clone能力。
關(guān)于deep copy還有個(gè)問(wèn)題:
There’s a problem you’ll encounter when trying to deep copy a composed object. You must assume that the clone( ) method in the member objects will in turn perform a deep copy on their references, and so on. This is quite a commitment. It effectively means that for a deep copy to work, you must either control all of the code in all of the classes, or at least have enough knowledge about all of the classes involved in the deep copy to know that they are performing their own deep copy correctly.
另外,出于性能上的考慮,我們通常不用object serialization/deserialization代替deep copy。
最后的一點(diǎn)是在clone時(shí)Immutable Class表現出來(lái)的特殊性。舉例來(lái)說(shuō),我們要clone一個(gè)java.util.ArrayList,這個(gè)ArrayList里面裝的是String,注意這是Immutable Class。clone過(guò)后新的ArrayList副本里面元素指向的還是原來(lái)那些String,沒(méi)錯。唯一的不同在于,對副本中的String進(jìn)行操作不會(huì )影響到原來(lái)的ArrayList中的String??雌饋?lái)好像是deep copy一樣,實(shí)際上只是由于副本中的String被操作時(shí)由于Immutable Class的性質(zhì)返回了新的String對象而已。很容易想清楚,不是嗎?
聯(lián)系客服