這篇東西我寫了好長時間,因為我把Thinking in Java, 3rd Ed中有關(guān)內(nèi)容仔細翻了個遍。E文版的看起來速度雖然慢,卻很引人思考。
Java平臺中有些類被稱為Immutable Class,因為這些類中的方法不能改變當前對象的值。
舉個例子,java.lang.String是個Immutable Class,如果我們這樣寫:
String str = "ZephyrFalcon";
str.substring(0, 6);
那么str的內(nèi)容是什么呢?沒錯,還是"ZephyrFalcon"。str.substring(0, 6)返回的確實是"Zephyr",但是它不能改變str指向的對象的的內(nèi)容,只不過返回一個新的String對象罷了,內(nèi)容是"Zephyr"。于是,單獨執(zhí)行String.substring()方法沒有效果,是沒有什么意義的,前面還要有個String reference引用它才行。
類似的,所有基本類型的Wrapper Class也全部是Immutable Class。
Immutable Class的特殊性質(zhì)會在我們進行Object Cloning的時候發(fā)揮特殊效果,呵呵。這我們最后再說。
Object Cloning的概念很簡單:復(fù)制一個對象。注意,是對象,也就是實例而不是類。All argument passing in Java is performed by passing references(Thinking in Java, 3rd Ed),除了primitives。另外,只有reference有作用域問題,object沒有。當我們需要保持原有對象,希望創(chuàng)建它的一個副本的時候(比如創(chuàng)建一個對象的local copy),cloning就派上用場了。
java.lang.Object類具有clone()這個方法,并且是protected權(quán)限(public > protected > default > private)。于是所有的Java類全都有clone()這個方法,默認情況是protected。這個protected權(quán)限在這里很tricky,它意味著兩件事:
1.默認情況下程序員無法對某個類簡單的使用clone(),這是由于權(quán)限問題不能access。(簡單使用指并非繼承這個類。)
2.我們無法通過對一個類的base class的reference來調(diào)用這個類的clone()。雖然有時候這很有用。比如當我們想要多態(tài)的(polymorphically)克隆一大批對象的時候。即這些對象是不同的類型,但是都是Object。我們可以用Object引用去refer它們,但是無法通過這些引用的clone()來復(fù)制這些對象。哦,丟人了...實際上這還是因為權(quán)限access問題...和上一個一樣,我的我的。
當然啦,如果你在一個沒有override clone()方法的類里調(diào)用這個類本身的clone(),也就是繼承的那個,當然是可以的。Object.clone()的行為非常神奇,它會對derived class object進行bitwise duplication,于是我們明明調(diào)用的是base class的clone()卻能復(fù)制出derived class object。
看來Java里的克隆比Heroes III里面的克隆魔法復(fù)雜多了!
結(jié)論就很自然了,如果我們不override clone()方法,并把其權(quán)限變成public的話,其他的類就無法使用它了(不明白自己翻Tutorial/Nutshell去)。當然了,在這個我們自己實現(xiàn)的clone()里,要調(diào)用super.clone()。
一旦某個類用上面提到的方法override了clone()方法,它的子類將自動擁有相應(yī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提供了克隆能力的標記接口。這個接口沒有任何方法,對,和java.io.Serializable一樣,它僅僅是個標記接口(tagging interface),標志著此類可以克隆。如果我們調(diào)用一個類的clone()方法,而這個類沒有實現(xiàn)此接口,則會在運行時拋出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,仍然指向原來它所指的對象。這稱為shadow copy。舉個最簡單的例子就是,一個容器/集合類,例如java.util.Hashtable被clone以后,它里面存放的引用仍然指向原來的對象。當原來的Hashtable中的對象改變時,新的Hashtable副本中的對象也會改變,原因是它們實際上保存的對象不是local copy,而是引用。
如果我們希望一個對象被clone后,它的成員域引用原來對象新的副本,我們只能在此對象的clone()方法里加入語句來實現(xiàn)。具體來說就是,對于希望引用新副本的成員域,把它原來引用的對象克隆,再讓這個成員域引用這個新副本。這稱為deep copy。遺憾的是deep copy并不總能成功,因為很多對象是不能克隆的。比如Java標準庫里面大多數(shù)類都沒有實現(xiàn)Cloneable接口。這是由于歷史原因。Java語言發(fā)明之初被用在機頂盒等設(shè)備上,而不是Internet上。這時候讓所有的對象都具有Clone能力是很合理的,于是clone()方法被放在java.lang.Object里,而且是public的。后來Java在Internet上流行起來,安全性的重要日益凸現(xiàn)。很多安全相關(guān)的對象不能被隨意復(fù)制。于是clone()變成了protected,還多了Cloneable接口,使類在實現(xiàn)的時候就能決定是否要擁有clone能力。
關(guān)于deep copy還有個問題:
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。
最后的一點是在clone時Immutable Class表現(xiàn)出來的特殊性。舉例來說,我們要clone一個java.util.ArrayList,這個ArrayList里面裝的是String,注意這是Immutable Class。clone過后新的ArrayList副本里面元素指向的還是原來那些String,沒錯。唯一的不同在于,對副本中的String進行操作不會影響到原來的ArrayList中的String??雌饋砗孟袷莇eep copy一樣,實際上只是由于副本中的String被操作時由于Immutable Class的性質(zhì)返回了新的String對象而已。很容易想清楚,不是嗎?