天天看點

面試官:你知道對象的克隆原理嗎?

本文主要内容

面試官:你知道對象的克隆原理嗎?

先說說生活中三個例子:

西遊記中,孫悟空能變身為n多個孫悟空,也就是一個孫悟空克隆為多個孫悟空了。

面試官:你知道對象的克隆原理嗎?

王者農藥中,元歌有個傀儡,這個傀儡我們也可了解為複制的元歌,你把傀儡殺死了,其實他自身根本沒死。

面試官:你知道對象的克隆原理嗎?

程式員把一份完整的代碼複制成多分,每一份都是獨立的一份完整的代碼。

以上三個例子便是我們今天要聊的話題,克隆(複制)。

入門案例

在Java語言中也有這麼一個複制的道理,請看下面的案例

下面我們進行複制:

這種複制也叫引用複制。關系如下:

面試官:你知道對象的克隆原理嗎?

我們把前面建立的UserDto對象引用複制給userDto了,然後又對對象中的兩個屬性進行重新指派,userDto1也會随着指派的。這就是所謂的淺克隆(淺複制)。

關于對象複制,在實際開發中用的還是蠻多的,比如說:UserDto複制給UserVo或者UserBo。

在淺複制中,如果原型對象的成員變量是值類型,将複制一份給目标對象;如果原型對象的成員變量是引用類型,則将引用對象的位址複制一份給目标對象,也就是說原型對象和目标對象的成員變量指向相同的記憶體位址。

簡單來說,在淺複制中,當對象被複制時隻複制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有複制。

面試官:你知道對象的克隆原理嗎?

在Java語言中,通過覆寫Object類的clone()方法加上實作Cloneable接口可以實作淺克隆。

前面說的孫悟空就是淺複制,因為你隻要把原本的那個孫悟空幹掉,其他也就不存在了,比如佛祖把孫悟空的原型按住,也就不存在多個孫悟空了。元歌的傀儡也是淺複制,因為我們把傀儡幹掉了,對于原型的元歌來說根本就沒有被幹掉。

既然有淺複制,那麼就會有深度複制嗎?

是的。

簡單版,模仿使用者資訊,一個是使用者位址類UserAddress和一個使用者資訊類User。

運作上面這段代碼,輸出結果為:

面試官:你知道對象的克隆原理嗎?

外面的User對象克隆是成功了,但是克隆出來的對象中,引用類型的屬性并沒有克隆出來,還是使用同一個引用位址。

在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都将複制一份給克隆對象,深克隆将原型對象的所有引用對象也複制一份給克隆對象。

簡單來說,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變量也将複制。

面試官:你知道對象的克隆原理嗎?

在Java語言中,如果需要實作深克隆,可以通過覆寫Object類的clone()方法實作再加上實作Cloneable接口,也可以通過序列化(Serialization)等方來實作。

如果引用類型裡面還包含很多引用類型,或者内層引用類型的類裡面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實作對象的深克隆。

序列化就是将對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在于記憶體中。通過序列化實作的拷貝不僅可以複制對象本身,而且可以複制其引用的成員對象,是以通過序列化将對象寫到一個流中,再從流裡将其讀出來,可以實作深克隆。

關于序列化實作深度複制,請看這篇文章:面試官:說說你對序列化的了解

下面使用Object的clone方法和實作Cloneable接口,寫一個深度複制案例:

先建立一個使用者位址類:

再建立一個使用者類:

運作這段代碼,輸出結果為:

面試官:你知道對象的克隆原理嗎?

user和user1以及它們内部的引用屬性都已經不是同一個了。這就是深度複制。

前面講的程式拷貝代碼,那就是深度複制,我們從githup拷貝一份代碼到本地,我愛怎麼改就怎麼改,别人管不着,也不影響别人代碼。

注意

Object 類的 clone 方法執行特定的複制操作。首先,如果此對象的類不能實作接口 Cloneable,則會抛出 CloneNotSupportedException。

所有的數組都被視為實作接口 Cloneable。否則,此方法會建立此對象的類的一個新執行個體,并像通過配置設定那樣,嚴格使用此對象相應字段的内容初始化該對象的所有字段;這些字段的内容沒有被自我複制。是以,此方法執行的是該對象的“淺表複制”,而不“深層複制”操作。

Spring中

這個工具類,使用頻率還是蠻高的,那它的對象複制是深複制還是淺複制呢?

運作這段代碼,輸出結果:

面試官:你知道對象的克隆原理嗎?

User的屬性userAddress還是使用同一個引用位址,是以屬于淺複制。在工作中需要留意這個點。

模式(Prototype Pattern)是用于建立重複的對象,同時又能保證性能。這種類型的設計模式屬于建立型模式,它提供了一種建立對象的最佳方式。

這種模式是實作了一個原型接口,該接口用于建立目前對象的克隆。當直接建立對象的代價比較大時,則采用這種模式。例如,一個對象需要在一個高代價的資料庫操作之後被建立。我們可以緩存該對象,在下一個請求時傳回它的克隆,在需要的時候更新資料庫,以此來減少資料庫調用。

原型模式的目的就是用原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。

優點:性能提高、逃避構造函數的限制。

缺點:

配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特别當一個類引用不支援串行化的間接對象,或者引用含有循環結構的時候。

深度克隆時必須實作 Cloneable 接口和重寫Object的clone方法,或者采取序列化方式。

對象的克隆或者複制就是原型模式的一種具體實作。

何為淺克隆或淺複制?何為深克隆或深複制?實作深度複制的方式有哪些?如何實作?對象的指派和原型模式有什麼關聯?