工作3年了,居然還搞不清楚Java的淺複製和深複製,老闆一頓痛批

今日分享開始啦,請大家多多指教~

今天給大家分享的是關於Java的深複製和淺複製,簡單來說就是建立一個和已知物件一模一樣的物件。可能日常編碼過程中用得不多,但是這是一個面試經常會問的問題,而且瞭解深複製和淺複製的原理,對於Java中的所謂值傳遞或者引用傳遞將會有更深的理解。

1。建立物件的5種方式

透過 new 關鍵字

這是最常用的一種方式,透過 new 關鍵字呼叫類的有參或無參構造方法來建立物件。

比如

Object obj = new Object();

透過 Class 類的 newInstance() 方法

這種預設是呼叫類的無參構造方法建立物件。

比如 Person p2 = (Person) Class。forName(“com。ys。test。Person”)。newInstance();

透過 Constructor 類的 newInstance 方法

這和第二種方法類似,都是透過反射來實現。透過

java。lang。relect。Constructor 類的 newInstance() 方法指定某個構造器來建立物件。

Person p3 = (Person) Person。class。getConstructors()[0]。newInstance();

實際上第二種方法利用 Class 的 newInstance() 方法建立物件,其內部呼叫還是 Constructor 的 newInstance() 方法。

利用 Clone 方法

Clone 是 Object 類中的一個方法,透過 物件A。clone() 方法會建立一個內容和物件 A 一模一樣的物件 B,clone 克隆,顧名思義就是建立一個一模一樣的物件出來。

Person p4 = (Person) p3。clone();

反序列化

序列化是把堆記憶體中的 Java 物件資料,透過某種方式把物件儲存到磁碟檔案中或者傳遞給其他網路節點(在網路上傳輸)。而反序列化則是把磁碟檔案中的物件資料或者把網路節點上的物件資料,恢復成Java物件模型的過程。

2。 Clone方法

在 Object。class 類中,原始碼為:

protected native Object clone() throws CloneNotSupportedException;

這是一個用 native 關鍵字修飾的方法,不理解也沒關係,只需要知道用 native 修飾的方法就是告訴作業系統,這個方法我不實現了,讓作業系統去實現。具體怎麼實現我們不需要了解,只需要知道 clone方法的作用就是複製物件,產生一個新的物件。那麼這個新的物件和原物件是什麼關係呢?

3。 基本型別和引用型別

在 Java 中資料型別可以分為兩大類:基本型別和引用型別。

基本型別也稱為值型別,分別是字元型別 char,布林型別 boolean以及數值型別 byte、short、int、long、float、double。

引用型別則包括類、介面、陣列、列舉等。

Java 將記憶體空間分為堆和棧。基本型別直接在棧中儲存數值,而引用型別是將引用放在棧中,實際儲存的值是放在堆中,透過棧中的引用指向堆中存放的資料。

上圖定義的 a 和 b 都是基本型別,其值是直接存放在棧中的;而 c 和 d 是 String 宣告的,這是一個引用型別,引用地址是存放在 棧中,然後指向堆的記憶體空間。

下面 d = c;這條語句表示將 c 的引用賦值給 d,那麼 c 和 d 將指向同一塊堆記憶體空間。

4。 淺複製

在講解淺複製之前,我們用一段程式碼來實現它。

上面的程式碼是一個我們要進行賦值的原始類 Person。下面我們產生一個 Person 物件,並呼叫其 clone 方法複製一個新的物件。

注意:呼叫物件的 clone 方法,必須要讓類實現 Cloneable 介面,並且覆寫 clone 方法。

首先看原始類 Person 實現 Cloneable 介面,並且覆寫 clone 方法,它還有三個屬性,一個引用型別 String定義的 pname,一個基本型別 int定義的 page,還有一個引用型別 Address ,這是一個自定義類,這個類也包含兩個屬性 pprovices 和 city 。

接著看測試內容,首先我們建立一個Person 類的物件 p1,其pname 為zhangsan,page為21,地址類 Address 兩個屬性為 湖北省和武漢市。接著我們呼叫 clone() 方法複製另一個物件 p2,接著列印這兩個物件的內容。

從第 1 行和第 3 行列印結果:

p1:com。bowen。demo。demo008。method1。Person@5dfe23e8  p2:com。bowen。demo。demo008。method1。Person@583fb274

可以看出這是兩個不同的物件。

從第 5 行和第 6 行列印的物件內容看,原物件 p1 和克隆出來的物件 p2 內容完全相同。

程式碼中我們只是更改了克隆物件 p2 的屬性 Address 為湖北省荊州市(原物件 p1 是湖北省武漢市) ,但是從第 7 行和第 8 行列印結果來看,原物件 p1 和克隆物件 p2 的 Address 屬性都被修改了。

也就是說物件 Person 的屬性 Address,經過 clone 之後,其實只是複製了其引用,他們指向的還是同一塊堆記憶體空間,當修改其中一個物件的屬性 Address,另一個也會跟著變化。

淺複製

:建立一個新物件,然後將當前物件的非靜態欄位複製到該新物件,如果欄位是值型別的,那麼對該欄位執行復制;如果該欄位是引用型別的話,則複製引用但不復制引用的物件。因此,原始物件及其副本引用同一個物件。

5。 深複製

弄清楚了淺複製,那麼深複製就很容易理解了。

深複製

:建立一個新物件,然後將當前物件的非靜態欄位複製到該新物件,無論該欄位是值型別的還是引用型別,都複製獨立的一份。當你修改其中一個物件的任何內容時,都不會影響另一個物件的內容。

那麼該如何實現深複製呢?Object 類提供的 clone 是隻能實現淺複製的。

6。 如何實現深複製?

深複製的原理我們知道了,就是要讓原始物件和克隆之後的物件所具有的引用型別屬性不是指向同一塊堆記憶體,這裡有兩種實現思路。

方法一:讓每個引用型別屬性內部都重寫clone() 方法

既然引用型別不能實現深複製,那麼我們將每個引用型別都拆分為基本型別,分別進行淺複製。比如上面的例子,Person 類有一個引用型別 Address(其實String 也是引用型別,但是String型別有點特殊,後面會詳細講解),我們在 Address 類內部也重寫 clone 方法。如下:

Address。class:

Person。class 的 clone() 方法:

@Override

protected Object clone() throws CloneNotSupportedException {

Person p = (Person) super。clone();

p。address = (Address) address。clone(); return p;

}

測試還是和上面一樣,我們會發現更改了p2物件的Address屬性,p1 物件的 Address 屬性並沒有變化。

但是這種做法有個弊端,這裡我們Person 類只有一個 Address 引用型別,而 Address 類沒有,所以我們只用重寫 Address 類的clone 方法,但是如果 Address 類也存在一個引用型別,那麼我們也要重寫其clone 方法,這樣下去,有多少個引用型別,我們就要重寫多少次,如果存在很多引用型別,那麼程式碼量顯然會很大,所以這種方法不太合適。

方法二:利用序列化

序列化是將物件寫到流中便於傳輸,而反序列化則是把物件從流中讀取出來。這裡寫到流中的物件則是原始物件的一個複製,因為原始物件還存在 JVM 中,所以我們可以利用物件的序列化產生克隆物件,然後透過反序列化獲取這個物件。

注意每個需要序列化的類都要實現 Serializable 介面,如果有某個屬性不需要序列化,可以將其宣告為 transient,即將其排除在克隆屬性之外。

因為序列化產生的是兩個完全獨立的物件,所以無論巢狀多少個引用型別,序列化都是能實現深複製的。

小結

本篇文章我們講解的是 Java 的深複製和淺複製,其實現方式正是透過呼叫 Object 類的 clone() 方法來完成。學習是無止境的,希望我分享的內容可以給大家帶來幫助!

今日份分享已結束,請大家多多包涵和指點!

相關文章