天天看點

0026 Java的深拷貝和淺拷貝【基礎】

1.淺複制與深複制概念

(1)淺複制(淺克隆)

被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複制僅僅複制所考慮的對象,而不複制它所引用的對象。

(2)深複制(深克隆)

被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量将指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複制把要複制的對象所引用的對象都複制了一遍。

2.Java的clone()方法

(1)clone方法将對象複制了一份并傳回給調用者。一般而言,clone()方法滿足:

①對任何的對象x,都有x.clone() !=x//克隆對象與原對象不是同一個對象

②對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣

③如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。

(2)Java中對象的克隆

①為了擷取對象的一份拷貝,我們可以利用Object類的clone()方法。

②在派生類中覆寫基類的clone()方法,并聲明為public。

③在派生類的clone()方法中,調用super.clone()。

④在派生類中實作Cloneable接口。

說明:

①為什麼我們在派生類中覆寫Object的clone()方法時,一定要調用super.clone()呢?在運作時刻,Object中的clone()識别出你要複制的是哪一個對象,然後為此對象配置設定空間,并進行對象的複制,将原始對象的内容一一複制到新對象的存儲空間中。

②繼承自java.lang.Object類的clone()方法是淺複制。沒法保證所引用的對象也是深複制,需要在覆寫Object的clone()方法裡調用所有引用對象的clone()方法,很顯然所有被引用的對象都需要實作Cloneable 接口,并且覆寫clone()方法。

3.利用串行化來做深複制

把對象寫到流裡的過程是串行化(Serilization)過程,但是在Java程式師圈子裡又非常形象地稱為“冷凍”或者“腌鹹菜(picking)”過程;而把對象從流中讀出來的并行化(Deserialization)過程則叫做 “解凍”或者“回鮮(depicking)”過程。

應當指出的是,寫在流裡的是對象的一個拷貝,而原對象仍然存在于JVM裡面,是以“腌成鹹菜”的隻是對象的一個拷貝,Java鹹菜還可以回鮮。

在Java語言裡深複制一個對象,常常可以先使對象實作Serializable接口,然後把對象(實際上隻是對象的一個拷貝)寫到一個流裡(腌成鹹菜),再從流裡讀出來(把鹹菜回鮮),便可以重建對象。

在Java中常用的拷貝操作有三個,operator = 、拷貝構造函數 和 clone()方法。

由于Java不支援運算符重載,我們無法在自己的自定義類型中定義operator=。拷貝構造函數大家應該很熟悉,現在看一下如何支援clone方法:

1)實作 Cloneable接口,因為 Object的 clone方法将檢查類是否實作了 Cloneable接口,如果沒有将抛出異常 CloneNotSupportedException對象。 Cloneable接口沒有任何方法,隻是個标志,是以隻需要簡單的寫上 implements Cloneable即可。

2)改寫從 Object繼承而來的 clone方法,使它的通路權限為 public,因為為了防止意外的支援 clone操作, Object的 clone方法是 protected權限。

通過上面的分析,可以看出,如果我們要給自己的類添加拷貝功能,我們可以添加拷貝構造函數和實作Cloneable接口。

 現在,來看一下不同的類型在拷貝過程中的表現:

Operator = 拷貝構造函數 clone方法
預定義非集合類型 深拷貝 如果支援拷貝構造函數的類型,則是深拷貝 不支援
自定義類型 淺拷貝 取決于實作 取決于實作
預定義集合類型 淺拷貝 會逐個調用每個元素的operator=方法
會逐個調用每個元素的operator=方法