細說 Java 的深拷貝和淺拷貝

版權聲明:spa

本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。code

未經容許,不得轉載。對象

1、前言

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

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

2、什麼是淺拷貝和深拷貝

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

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

而淺拷貝和深拷貝就是在這個基礎之上作的區分,若是在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有真實的建立一個新的對象,則認爲是淺拷貝。反之,在對引用數據類型進行拷貝的時候,建立了一個新的對象,而且複製其內的成員變量,則認爲是深拷貝。hash

因此到如今,就應該瞭解了,所謂的淺拷貝和深拷貝,只是在拷貝對象的時候,對 類的實例對象 這種引用數據類型的不一樣操做而已。it

總結來講:io

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

/clone-qian.png

二、深拷貝:對基本數據類型進行值傳遞,對引用數據類型,建立一個新的對象,並複製其內容,此爲深拷貝。

/clone-深.png

3、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() 。

4、總結

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

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

公衆號二維碼.jpg

相關文章
相關標籤/搜索