深拷貝(clone)、淺拷貝
簡易理解:
淺拷貝:只拷貝源對象的地址,所以源對象的任何值變化,拷貝對象值也隨着變化。
深拷貝:拷貝對象的值,而不是地址,當源對象的值發生變化,拷貝對象的值不會發生變化
文章目錄
淺拷貝
例子展示:
先建立一個對象
可以看見,淺拷貝改變源對象的值,拷貝對象值也會發生改變
深拷貝
爲啥我們需要深拷貝呢?在某些業務場景下,我們需要完全相同但是缺不相關聯的對象。其實大體有兩種,Serializable與Cloneable
深拷貝的幾種方式:
- 構造函數方式
- 重寫clone方法
- Apache Commons Lang 序列化
- Gson序列化
- Jackson序列化
構造函數完成深拷貝
這是通過new的方式,對系統的開銷比較大。它倆是隔離的
重寫clone()方法
它是Object中的。這是一個native方法,即用非java語言編寫的,從一方面來將它的效率更高。
可以看見它是 protected修飾的,該修飾符的訪問範圍。複習一下
訪問級別 | 訪問控制符 | 同類 | 同包 | 子類 | 不同包 |
---|---|---|---|---|---|
公開 | public | true | true | true | ture |
受保護 | protected | true | true | true | false |
默認 | 沒有修飾符 | true | true | false | false |
私有 | private | true | false | false | false |
所以要想克隆對象,要實現Cloneable接口。即告訴虛擬機我們可以利用這個對象完成深拷貝
可以看見它是沒有任何抽象方法的
可以看見,我們現在還不能clone,還需要重寫方法。因爲Cloneable它僅僅是一個標誌,你可以看見,它裏面是空的。
來實現Object的clone方法
但是,我們還需要修改三個地方。訪問修飾符和返回對象的類型
protected解釋,例子
- 這裏訪問修飾符由 procted改爲了 public。這裏複習一下java基礎:子類重寫父類的方法時訪問修飾符要大於等於父類的,否則就破壞了繼承規則:本來父類的某個方法是可以訪問的,到了子類這裏就不可以訪問了。
具體一點,procted是訪問修飾符裏面最複雜的。
準備一個 java0包中的A03,A02,java包中的A01與A02
在同一個 包中:
- 在(子類[允許被繼承]、父類)可直接使用
子類:
子類的mian方法也可以訪問
父類:
- 子類外,可以通過(子類、父類)【對象 .】
不在同一個包中:
- 當前類外,不能使用 **【對象 .】**的方式。
可見到沒有 A03 類中的 a03 成員
- 子類中,可繼承protected成員,且在非main方法中可直接使用名稱進行訪問。在子類的main方法中可(子類) **【對象.】**調用,但是不能(父類)調用,這一點有點像 序號1的內容(相當於父類跨包)。
- 子類外,與子類同包。通過子類打點方式調用這個方式調用protected是不行的,更別說父類了。需要重寫這個成員或方法。
來重新加上,這樣肯定就可以了,相當於之前的同包
4.在使用子類的類中,與父類同包,(子類,父類)都可以通過打點的方式獲得
以上結果需把之前重寫的註釋掉
當然這是把A02之前覆蓋A03的procted刪去了的,不然又是不同包訪問了某個類類中的procted了,會報錯。這是不同包中 1 的結論
爲什麼colone要重寫且改成public
-
與子類與父類都不同包,若想通過子類訪問父類的procted,則需要重寫該成員並將訪問修飾符改爲 public。 !!!!!說了那麼多,其實這纔是跟我們要講的clone相關的,前面只是帶大家複習一下
爲什麼呢?前面不是說了嗎,不同包不能打點使用procted,那就只能比他上一級咯,private->default->procted->public。但是clone()是Object的,我們繼承過來的,想想 我們上面的例子 **不同包中的第三點,與子類同包,與子類不同類調用子類打點的方式需要重寫這個protected。因爲不然相當於父類的成員跨包,**現在在前面的基礎上改成了與子類不同包了,類比一下肯定是要重寫方法的,好像感覺難度也增加了。那麼現在有第三個包了,我們不可能在第三個包又加這個子類吧,所以我們就可以把子類重寫的procted訪問修飾符改爲public就可以了。
當然這個可以幫助我們達到第三個包訪問的目的,但是訪問修飾符改變了,這個需要我們自己去權衡。
在開發中這種情況很常見。往往我們的一個對象(默認繼承了java.lang.Object)在一個entity包下,然後又在另一個包下使用這個bean,這樣就是三個包了
示例:(三個不同包下)
會報錯,此時將A02中的protected改爲public。結果顯而易見
clone()方法
但是,我們發現地址不一樣。成功。看起來比兩個new簡潔。
如果Person有多個屬性,通過new的方式看起來是不是很噁心:
後期修改就比較麻煩。
但是呢,它不能完成成員中有對象的clone除了【String】,否則對象就是淺拷貝,這是他的弊端就凸顯出來了。下面舉例:
當clone()對象的成員中存在一個對象時
準備A02【包含對象A01】:
A01:
結果:
可以發現,clone是clone了,但是裏面的對象是淺拷貝。修改clone的也修改了原來的。那麼在這種情況下怎麼進行深拷貝呢?
那麼我們想想是不是應該把A01也就是它的這個成員對象也變爲clone呢?試一試。
可以看見,還是不行。類比這麼想一下,我們之前A02重寫了clone方法,在Test中克隆的,顯式調用了clone,所以才深拷貝了A02。
現在A02中有A01對象了,是也要對A01進行深拷貝的,是不是也該在A02中的clone方法顯式調用一下呢?
結果:
至於爲什麼這麼做,我還不能做出很好的解釋[之後補上]。不過通過這樣的例子你應該可以很好地快速記憶,知道如何使用了。
那麼如果沒有實現Cloneable接口,重寫了clone方法呢?
會報 CloneNotSupportedException異常
如果沒重寫clone()方法,實現了Cloneable接口呢?。。。那肯定沒clone方法啊。其實我還是覺得clone()比較麻煩,使用起來要多加小心,例如你必須對所有的對象都要有所瞭解。那麼有沒有其他方式呢?其實也就是Serializable,現在有很多框架都對序列化進行了封裝,方便我們使用。
如果對象很複雜,多個繼承怎麼辦,用clone就比較麻煩了。
可以考慮通過序列化的方式。
本篇文章不講框架的使用,主要是幫助大家理解深拷貝是什麼東西?
至於序列化與框架使用,之後章節詳細講解。