1. 程式人生 > >Java學習之——深複製與淺複製

Java學習之——深複製與淺複製

物件的建立

要想理解什麼是深複製(深拷貝)和淺複製(淺拷貝)我們首先要知道物件是怎麼建立的。Java建立物件的方式有兩種:

1.使用new關鍵字來建立物件,2.使用clone方法來複制物件

那麼這兩種方式有什麼相同和不同呢? new操作符的本意是分配記憶體。程式執行到new操作符時, 首先去看new操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化,構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。而clone在第一步是和new相似的, 都是分配記憶體,呼叫clone方法時,分配的記憶體和源物件(即呼叫clone方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域, 填充完成之後,clone方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。

public class Person{
    int age;
    String name;
}

複製物件or複製引用

Person p1 = new Person("zhang",23);
Person p2 = p1;

首先我們來看上面一段程式碼,其中new Person()是建立了一個真正的物件,p1和p2只是兩個物件的引用。通過列印地址可以發現地址值是相同的,既然地址都是相同的,那麼肯定是同一個物件。p1和p2只是指向了一個相同的物件的兩個不同的引用而已。可以把這種現象叫做引用的複製。記憶體中的地址情景如圖:

要想真正的複製一個物件可以使用clone方法

Person p1 = new Person("zhang",23);
Person p2 = (Person)p1.clone();

以上程式碼執行後記憶體中的地址情景如圖:

可以看出實現了對一個物件的真正的複製。

深拷貝and淺拷貝

Person類中有兩個欄位(屬性/成員變數),年齡欄位很簡單是基本資料型別,所以對它的拷貝沒有什麼疑議,直接將一個4位元組的整數值拷貝過來就行。但是name是String型別的, 它只是一個引用, 指向一個真正的String物件,那麼對它的拷貝有兩種方式: 直接將源物件中的name的引用值拷貝給新物件的name欄位, 或者是根據原Person物件中的name指向的字串物件建立一個新的相同的字串物件,將這個新字串物件的引用賦給新拷貝的Person物件的name欄位。這兩種拷貝方式分別叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如圖:

如果只是用Object中預設的clone方法,是淺拷貝的,為了要在clone物件時進行深拷貝, 那麼就要Clonable介面,覆蓋並實現clone方法,除了呼叫父類中的clone方法得到新的物件, 還要將該類中的引用變數也clone出來。

Clone的用法和說明

(1)clone方法將物件複製了一份並返回給呼叫者。一般而言,clone()方法滿足:  對任何的物件x,都有x.clone() !=x 克隆物件與原物件不是同一個物件  對任何的物件x,都有x.clone().getClass()= =x.getClass()克隆物件與原物件的型別一樣  如果物件x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。 

(2)為了獲取物件的一份拷貝,我們可以利用Object類的clone()方法。 在派生類中覆蓋基類的clone()方法,並宣告為public。 在派生類的clone()方法中,呼叫super.clone()。 在派生類中實現Cloneable介面。

說明:

(1)為什麼我們在派生類中覆蓋Object的clone()方法時,一定要呼叫super.clone()呢?在執行時刻,Object中的clone()識別出你要複製的是哪一個物件,然後為此物件分配空間,並進行物件的複製,將原始物件的內容一一複製到新物件的儲存空間中。

(2)那麼clone類為什麼還要實現Cloneable介面呢?稍微注意一下,Cloneable介面是不包含任何方法的!其實這個介面僅僅是一個標誌,而且這個標誌也僅僅是針對Object類中clone()方法的,如果clone類沒有實現Cloneable介面,並呼叫了Object的clone()方法(也就是呼叫了super.Clone()方法),那麼Object的clone()方法就會丟擲CloneNotSupportedException異常。

(3)利用序列化也可以實現對物件的深複製