天天看點

避免對象的淺拷貝

我們知道一個類實作了Cloneable接口就表示它具備了被拷貝的能力,如果再覆寫clone()方法就會完全具備拷貝能力。拷貝是在記憶體中進行的,是以在性能方面比直接通過new生成對象要快很多,特别是在大對象的生成上,這會使性能的提升非常顯著。但是對象拷貝也有一個比較容易忽略的問題:淺拷貝(Shadow Clone,也叫做影子拷貝)存在對象屬性拷貝不徹底的問題。我們來看這樣一段代碼:

避免對象的淺拷貝
避免對象的淺拷貝

程式中,我們描述了這樣一個場景:一個父親,有兩個兒子,大小兒子同根同種,是以小兒子對象就通過拷貝大兒子對象來生成,運作輸出的結果如下:

這很正确,沒有問題。突然有一天,父親心血來潮想讓大兒子去認個幹爹,也就是大兒子的父親名稱需要重新設定一下,代碼如下:

避免對象的淺拷貝
避免對象的淺拷貝

上面僅僅修改了加粗字型部分,大兒子重新設定了父親名稱,我們期望的輸出是:将大兒子父親的名稱修改為幹爹,小兒子的父親名稱保持不變。下面來檢查一下結果是否如此:

怎麼回事,小兒子的父親也成了“幹爹”?兩個兒子都沒有,豈不是要氣死“父親”了!出現這個問題的原因就在于clone方法,我們知道所有類都繼承自Object,Object提供了一個對象拷貝的預設方法,即上面代碼中的super.clone方法,但是該方法是有缺陷的,它提供的是一種淺拷貝方式,也就是說它并不會把對象的所有屬性全部拷貝一份,而是有選擇性的拷貝,它的拷貝規則如下:

(1)基本類型

如果變量是基本類型,則拷貝其值,比如int、float等。

(2)對象

如果變量是一個執行個體對象,則拷貝位址引用,也就是說此時新拷貝出的對象與原有對象共享該執行個體變量,不受通路權限的限制。這在Java中是很瘋狂的,因為它突破了通路權限的定義:一個private修飾的變量,竟然可以被兩個不同的執行個體對象通路,這讓Java的通路權限體系情何以堪!

(3)String字元串

這個比較特殊,拷貝的也是一個位址,是個引用,但是在修改時,它會從字元串池(String Pool)中重新生成新的字元串,原有的字元串對象保持不變,在此處我們可以認為String是一個基本類型。(有關字元串的知識詳見第4章。)

明白了這三個規則,上面的例子就很清晰了,小兒子對象是通過拷貝大兒子産生的,其父親都是同一個人,也就是同一個對象,大兒子修改了父親名稱,小兒子也就跟着修改了—于是,父親的兩個兒子都沒了!其實要更正也很簡單,clone方法的代碼如下:

避免對象的淺拷貝
避免對象的淺拷貝

然後再運作,小兒子的父親就不會是“幹爹”了。如此就實作了對象的深拷貝(Deep Clone),保證拷貝出來的對象自成一體,不受“母體”的影響,和new生成的對象沒有任何差別。

注意 淺拷貝隻是Java提供的一種簡單拷貝機制,不便于直接使用。

本文轉自SummerChill部落格園部落格,原文連結:http://www.cnblogs.com/DreamDrive/p/5430479.html,如需轉載請自行聯系原作者