細說 Java 的深拷貝和淺拷貝


版權聲明:

本賬號發布文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。

未經允許,不得轉載。

一、前言

任何變成語言中,其實都有淺拷貝和深拷貝的概念,Java 中也不例外。在對一個現有的對象進行拷貝操作的時候,是有淺拷貝和深拷貝之分的,他們在實際使用中,區別很大,如果對其進行混淆,可能會引發一些難以排查的問題。

本文就在 Java 中的深拷貝和淺拷貝做一個詳細的解說。

二、什么是淺拷貝和深拷貝

首先需要明白,淺拷貝和深拷貝都是針對一個已有對象的操作。那先來看看淺拷貝和深拷貝的概念。

在 Java 中,除了基本數據類型(元類型)之外,還存在 類的實例對象 這個引用數據類型。而一般使用 『 = 』號做賦值操作的時候。對於基本數據類型,實際上是拷貝的它的值,但是對於對象而言,其實賦值的只是這個對象的引用,將原對象的引用傳遞過去,他們實際上還是指向的同一個對象。

而淺拷貝和深拷貝就是在這個基礎之上做的區分,如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有真實的創建一個新的對象,則認為是淺拷貝。反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且復制其內的成員變量,則認為是深拷貝。

所以到現在,就應該了解了,所謂的淺拷貝和深拷貝,只是在拷貝對象的時候,對 類的實例對象 這種引用數據類型的不同操作而已。

總結來說:

1、淺拷貝:對基本數據類型進行值傳遞,對引用數據類型進行引用傳遞般的拷貝,此為淺拷貝。

/clone-qian.png

2、深拷貝:對基本數據類型進行值傳遞,對引用數據類型,創建一個新的對象,並復制其內容,此為深拷貝。

/clone-深.png

三、Java 中的 clone()

3.1 Object 上的 clone() 方法

在 Java 中,所有的 Class 都繼承自 Object ,而在 Object 上,存在一個 clone() 方法,它被聲明為了 protected ,所以我們可以在其子類中,使用它。

而無論是淺拷貝還是深拷貝,都需要實現 clone() 方法,來完成操作。

/clone-method.png

可以看到,它的實現非常的簡單,它限制所有調用 clone() 方法的對象,都必須實現 Cloneable 接口,否者將拋出 CloneNotSupportedException 這個異常。最終會調用 internalClone() 方法來完成具體的操作。而 internalClone() 方法,實則是一個 native 的方法。對此我們就沒必要深究了,只需要知道它可以 clone() 一個對象得到一個新的對象實例即可。

/clone-cloneable.png

而反觀 Cloneable 接口,可以看到它其實什么方法都不需要實現。對他可以簡單的理解只是一個標記,是開發者允許這個對象被拷貝。

3.2 淺拷貝

先來看看淺拷貝的例子。

首先創建一個 class 為 FatherClass ,對其實現 Cloneable 接口,並且重寫 clone() 方法。

/clone-father01.png

然后先正常 new 一個 FatherClass 對象,再使用 clone() 方法創建一個新的對象。

/clone-Demo1.png

最后看看輸出的 Log :

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 560973324
I/cxmyDev: fatherB hash : 560938740
I/cxmyDev: fatherA name : 張三
I/cxmyDev: fatherB name : 張三

可以看到,使用 clone() 方法,從 ==hashCode 的不同可以看出,clone() 方法實則是真的創建了一個新的對象。

但這只是一次淺拷貝的操作。

來驗證這一點,繼續看下去,在 FatherClass 中,還有一個 ChildClass 的對象 child ,clone() 方法是否也可以正常復制它呢?改寫一個上面的 Demo。

/clone-Demo2.png

看到,這里將其內的 child 進行負責,用起來看看輸出的 Log 效果。

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 560975188
I/cxmyDev: fatherB hash : 560872384
I/cxmyDev: fatherA name : 張三
I/cxmyDev: fatherB name : 張三
I/cxmyDev: ==================
I/cxmyDev: A.child == B.child : true
I/cxmyDev: fatherA.child hash : 560891436
I/cxmyDev: fatherB.child hash : 560891436

從最后對 child 的輸出可以看到,A 和 B 的 child 對象,實際上還是指向了統一個對象,只對對它的引用進行了傳遞。

3.3 深拷貝

既然已經了解了對 clone() 方法,只能對當前對象進行淺拷貝,引用類型依然是在傳遞引用。

那么,如何進行一個深拷貝呢?

比較常用的方案有兩種:

  1. 序列化(serialization)這個對象,再反序列化回來,就可以得到這個新的對象,無非就是序列化的規則需要我們自己來寫。
  2. 繼續利用 clone() 方法,既然 clone() 方法,是我們來重寫的,實際上我們可以對其內的引用類型的變量,再進行一次 clone()。

繼續改寫上面的 Demo ,讓 ChildClass 也實現 Cloneable 接口。

/clone-child1.png

最重要的代碼就在 FatherClass.clone() 中,它對其內的 child ,再進行了一次 clone() 操作。

再來看看輸出的 Log。

I/cxmyDev: fatherA == fatherB : false
I/cxmyDev: fatherA hash : 561056732
I/cxmyDev: fatherB hash : 561057344
I/cxmyDev: fatherA name : 張三
I/cxmyDev: fatherB name : 張三
I/cxmyDev: ==================
I/cxmyDev: A.child == B.child : false
I/cxmyDev: fatherA.child hash : 561057304
I/cxmyDev: fatherB.child hash : 561057360

可以看到,對 child 也進行了一次拷貝,這實則是對 ChildClass 進行的淺拷貝,但是對於 FatherClass 而言,則是一次深拷貝。

其實深拷貝的思路都差不多,序列化也好,使用 clone() 也好,實際上都是需要我們自己來編寫拷貝的規則,最終實現深拷貝的目的。

如果想要實現深拷貝,推薦使用 clone() 方法,這樣只需要每個類自己維護自己即可,而無需關心內部其他的對象中,其他的參數是否也需要 clone() 。

四、總結

到現在基本上就已經梳理清楚,Java 中淺拷貝和深拷貝的概念了。

實則淺拷貝和深拷貝只是相對的,如果一個對象內部只有基本數據類型,那用 clone() 方法獲取到的就是這個對象的深拷貝,而如果其內部還有引用數據類型,那用 clone() 方法就是一次淺拷貝的操作。

公眾號二維碼.jpg


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM