深拷貝(clone)、淺拷貝

深拷貝(clone)、淺拷貝

簡易理解:

淺拷貝:只拷貝源對象的地址,所以源對象的任何值變化,拷貝對象值也隨着變化。

深拷貝:拷貝對象的值,而不是地址,當源對象的值發生變化,拷貝對象的值不會發生變化

淺拷貝

例子展示:

先建立一個對象
在這裏插入圖片描述
在這裏插入圖片描述
可以看見,淺拷貝改變源對象的值,拷貝對象值也會發生改變

深拷貝

爲啥我們需要深拷貝呢?在某些業務場景下,我們需要完全相同但是缺不相關聯的對象。其實大體有兩種,Serializable與Cloneable

深拷貝的幾種方式:

  1. 構造函數方式
  2. 重寫clone方法
  3. Apache Commons Lang 序列化
  4. Gson序列化
  5. 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

image-20200609124919263

在同一個 包中:
  1. 在(子類[允許被繼承]、父類)可直接使用

子類:

image-20200609125336319

子類的mian方法也可以訪問

image-20200609131751536

父類:
image-20200609125401692

  1. 子類外,可以通過(子類、父類)【對象 .】

image-20200609125806052

不在同一個包中:
  1. 當前類外,不能使用 **【對象 .】**的方式。

image-20200609124734019

可見到沒有 A03 類中的 a03 成員

  1. 子類中,可繼承protected成員,且在非main方法中可直接使用名稱進行訪問。在子類的main方法中可(子類) **【對象.】**調用,但是不能(父類)調用,這一點有點像 序號1的內容(相當於父類跨包)。

image-20200609132039340

  1. 子類外,與子類同包。通過子類打點方式調用這個方式調用protected是不行的,更別說父類了。需要重寫這個成員或方法。

image-20200609132728703

來重新加上,這樣肯定就可以了,相當於之前的同包

image-20200609132805755

image-20200609132857896

4.在使用子類的類中,與父類同包,(子類,父類)都可以通過打點的方式獲得

image-20200609133128516

以上結果需把之前重寫的註釋掉

image-20200609133236547

當然這是把A02之前覆蓋A03的procted刪去了的,不然又是不同包訪問了某個類類中的procted了,會報錯。這是不同包中 1 的結論

爲什麼colone要重寫且改成public
  1. 與子類與父類都不同包,若想通過子類訪問父類的procted,則需要重寫該成員並將訪問修飾符改爲 public!!!!!說了那麼多,其實這纔是跟我們要講的clone相關的,前面只是帶大家複習一下

    爲什麼呢?前面不是說了嗎,不同包不能打點使用procted,那就只能比他上一級咯,private->default->procted->public。但是clone()是Object的,我們繼承過來的,想想 我們上面的例子 **不同包中的第三點,與子類同包,與子類不同類調用子類打點的方式需要重寫這個protected。因爲不然相當於父類的成員跨包,**現在在前面的基礎上改成了與子類不同包了,類比一下肯定是要重寫方法的,好像感覺難度也增加了。那麼現在有第三個包了,我們不可能在第三個包又加這個子類吧,所以我們就可以把子類重寫的procted訪問修飾符改爲public就可以了。

    當然這個可以幫助我們達到第三個包訪問的目的,但是訪問修飾符改變了,這個需要我們自己去權衡。

    在開發中這種情況很常見。往往我們的一個對象(默認繼承了java.lang.Object)在一個entity包下,然後又在另一個包下使用這個bean,這樣就是三個包了

示例:(三個不同包下)
image-20200609135444883

image-20200609135503806

會報錯,此時將A02中的protected改爲public。結果顯而易見

image-20200609135546245

clone()方法

在這裏插入圖片描述

但是,我們發現地址不一樣。成功。看起來比兩個new簡潔。

如果Person有多個屬性,通過new的方式看起來是不是很噁心:
在這裏插入圖片描述

後期修改就比較麻煩。

但是呢,它不能完成成員中有對象的clone除了【String】,否則對象就是淺拷貝,這是他的弊端就凸顯出來了。下面舉例:

當clone()對象的成員中存在一個對象時

準備A02【包含對象A01】:

image-20200610100800469

A01:

image-20200610100814682

結果:

image-20200610100929148

可以發現,clone是clone了,但是裏面的對象是淺拷貝。修改clone的也修改了原來的。那麼在這種情況下怎麼進行深拷貝呢?

那麼我們想想是不是應該把A01也就是它的這個成員對象也變爲clone呢?試一試。

image-20200610101414241

可以看見,還是不行。類比這麼想一下,我們之前A02重寫了clone方法,在Test中克隆的,顯式調用了clone,所以才深拷貝了A02。

現在A02中有A01對象了,是也要對A01進行深拷貝的,是不是也該在A02中的clone方法顯式調用一下呢?

image-20200610112920610

結果:

image-20200610112953175

至於爲什麼這麼做,我還不能做出很好的解釋[之後補上]。不過通過這樣的例子你應該可以很好地快速記憶,知道如何使用了。

那麼如果沒有實現Cloneable接口,重寫了clone方法呢?

image-20200608211619116

會報 CloneNotSupportedException異常

如果沒重寫clone()方法,實現了Cloneable接口呢?。。。那肯定沒clone方法啊。其實我還是覺得clone()比較麻煩,使用起來要多加小心,例如你必須對所有的對象都要有所瞭解。那麼有沒有其他方式呢?其實也就是Serializable,現在有很多框架都對序列化進行了封裝,方便我們使用。

如果對象很複雜,多個繼承怎麼辦,用clone就比較麻煩了。

可以考慮通過序列化的方式。

本篇文章不講框架的使用,主要是幫助大家理解深拷貝是什麼東西?

至於序列化與框架使用,之後章節詳細講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章