search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

java拷貝機制詳解

在java中,拷貝分為深拷貝和淺拷貝兩種。java在公共超類Object中實現了一種叫做clone的方法,這種方法clone出來的新對象為淺拷貝,而通過自己定義的clone方法為深拷貝。

(一)Object中clone方法

如果我們new出一個新對象,用一個聲明去引用它,之後又用另一個聲明去引用前一個聲明,那麼最後的結果是:這兩個聲明的變數將指向同一個對象,一處被改全部被改。如果我們想創建一個對象的copy,這個copy和對象的各種屬性完全相同,而且修改這個copy和原對象毫無關係,那麼這個時候我們就要用到clone方法。

package Clone; import java.util.Date; /** * * @author QuinnNorris * java中的兩種拷貝機制 */publicclassClone {/** * @param args * @throws CloneNotSupportedException */publicstaticvoidmain(String args) throws CloneNotSupportedException { // TODO Auto-generated method stub ClassA valA = new ClassA(1, "old", new Date); // 聲明一個新的ClassA對象,我們不需要太關注ClassA的功能 ClassA valB = valA; // 將valA引用的對象賦給valB valA.setObject("new"); // 更改valA中的值,此時valB也被更改了,因為valA和valB指向同一個對象 valB = valA.clone;//通過clone方法製造副本 } }

ClassA類中關於clone方法的重寫部分:

//需要實現Cloneable介面publicclassClassAimplementsCloneable {public ClassA clone throws CloneNotSupportedException { return (ClassA) super.clone;//調用父類(Object)的clone方法 } }1.如何使用Object中clone方法的

有人總結使用clone方法的四條法則,我們一起分享一下:

  • 為了獲取對象的一份拷貝,我們可以利用Object類的clone方法。
  • 在派生類中覆蓋基類的clone方法,並聲明為public。
  • 在派生類的clone方法中,調用super.clone。
  • 在派生類中實現Cloneable介面。
2.protected修飾的clone方法

在java.lang.Object的中,他將clone方法設置為protected修飾,這是很特殊的一種情況。protected的作用域是:包可見+可繼承。之所以這樣設置,是因為這個方法要返回的是克隆出來的對象,即clone方法要去克隆的類型是未知的,沒有辦法確定返回值的類型,自然只能讓子孫後代來實現它重寫它,為了能夠讓後代繼承而又不過與張開,設置為了protected類型。

3.實現clone方法需要實現Cloneable介面

那麼我們重寫clone方法的時候為什麼要去實現Cloneable介面呢?事實上,Cloneable介面是java中的一個標記介面,標記介面是指那些沒有方法和屬性的介面,他們存在只是為了讓大家知道一些信息,而且在用:xxx instanceof Cloneable 的時候可以進行判斷。Cloneable這個介面的出現就是為了讓設計者知道要進行克隆處理了。如果一個對象需要克隆,但是沒有實現(實際上,這裡的「實現」換成「寫上」更準確)Cloneable介面,那麼會產生一個已檢驗異常。

4.實現clone方法需要調用父類的clone

我們為了達到複製一個和調用方法的這個對象一模一樣的對象的目的,我們需要使用父類的clone方法,父類也以此類推,知道達到了Object的clone方法,那麼Object的clone方法有什麼用呢?API中是這樣說的:

protected Object clone throws CloneNotSupportedException
創建並返回此對象的一個副本。
「副本」的準確含義可能依賴於對象的類。這樣做的目的是,對於任何對象 x,
表達式: x.clone != x為 true
表達式: x.clone.getClass == x.getClass也為 true
但這些並非必須要滿足的要求。
一般情況下:
x.clone.equals(x)為 true,但這並非必須要滿足的要求。
按照慣例,返回的對象應該通過調用 super.clone 獲得。
如果一個類及其所有的超類(Object 除外)都遵守此約定,則 x.clone.getClass == x.getClass

上面就是API中對clone的一部分基本講解。我們可以得出結論的是,只要合理的調用了spuer.clone它就會返回一個被克隆的對象。在運行時刻,Object中的clone識別出你要複製的是哪一個對象,然後為此對象分配空間,並進行對象的複製,將原始對象的內容一一複製到新對象的存儲空間中。在這個克隆對象中,所有的屬性都和被克隆的對象的屬性相同,而這些相同的屬性分為兩種:

第一種 : 八大原始類型和不可變的對象(比如String)
第二種 : 其他類對象

對於第一種,clone方法將他們的值設置為原對象的值,沒有任何問題。對於第二種,clone方法只是簡單的將複製的新對象的引用指向原對象指向的引用,第二種的類對象會被兩個對象修改。那麼這個時候就涉及一個深淺拷貝的概念了。

(二)淺拷貝

淺拷貝:被拷貝對象的所有變數都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不複製它所引用的對象。

比如舉個例子,一個類A中有另外一個類B類型的變數。在A重寫clone函數調用super.clone的時候,創建的新對象和原來對象中的類B類型的變數是同一個,他們指向了同一個B的類型變數。如果在A中對B的變數做了修改,在新的拷貝出來的對象中B的變數也會被同樣的修改。

請記住,直接調用super.clone實現的clone方法全部都是淺拷貝。

(三)深拷貝

深拷貝:被拷貝對象的所有變數都含有與原來的對象相同的值,除去那些引用其他對象的變數。那些引用其他對象的變數將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。

通俗的說,如果說淺拷貝,開始的時候是兩條線,如果在最後有一個其他類的變數,那麼這兩條線最後會合二為一,共同指向這變數,都能對他進行操作。深拷貝則是完完全全的兩條線,互不干涉,因為他已經把所有的內部中的變數的對象全都複製一遍了。

深拷貝在代碼中,需要在clone方法中多書寫調用這個類中其他類的變數的clone函數。

(四)串列化深拷貝

在框架中,有的時候我們發現其中並沒有重寫clone方法,那麼我們在需要拷貝一個對象的時候是如何去操作的呢?答案是我們經常會使用串列化方法,實現Serializable介面。

去尋找其他的方法來替代深拷貝也是無可奈何的事情,如果採用傳統的深拷貝,難道你拷貝一個對象的時候向其中追無數層來拷貝完所有的對象變數么?先不談這麼做的時間消耗,僅僅是寫這樣的代碼都會讓人望而生畏。串列化深拷貝就是這樣一個相對簡單的方法。

把對象寫到流里的過程是串列化(Serilization)過程,但是在Java程序師圈子裡又非常形象地稱為「冷凍」或者「腌鹹菜(picking)」過程;而把對象從流中讀出來的并行化(Deserialization)過程則叫做 「解凍」或者「回鮮(depicking)」過程。應當指出的是,寫在流里的是對象的一個拷貝,而原對象仍然存在於JVM裡面,因此「腌成鹹菜」的只是對象的一個拷貝,Java鹹菜還可以回鮮。

上面是網上的專業解釋,我也不在這裡班門弄斧了。在Java語言里深複製一個對象,常常可以先使對象實現Serializable介面,然後把對象(實際上只是對象的一個拷貝)寫到一個流里(腌成鹹菜),再從流里讀出來(把鹹菜回鮮),便可以重建對象。

public Object deepClone { //寫入對象 ByteArrayOutoutStream bo=new ByteArrayOutputStream; ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //讀取對象 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject); }

雖然這種學院派的代碼看起來很複雜,其實只是把對象放到流里,再拿出來。相比較分析判斷無數的clone,這樣簡直是再簡單不過了。這樣做的前提是對象以及對象內部所有引用到的對象都是可串列化的,否則,就需要仔細考察那些不可串列化的對象是否設成transient。

transient:一個對象只要實現了Serilizable介面,這個對象就可以被序列化(序列化是指將java代碼以位元組序列的形式寫出,即我們上面代碼前三行寫入對象),Java的這種序列化模式為開發者提供了很多便利,可以不必關係具體序列化的過程,只要這個類實現了Serilizable介面,這個的所有屬性和方法都會自動序列化。但是有種情況是有些屬性是不需要序列號的,所以就用到這個關鍵字。只需要實現Serilizable介面,將不需要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。

(五)總結

在實際的應用中,深拷貝和淺拷貝只是兩個概念,不一定誰比誰好,要按照實際的工作來確定如何去拷貝一個對象。如果在資料庫操作方面,為了取出一張表時不涉及其他的表,肯定需要使用淺拷貝,而在框架的Serializable中,雖然耗時,但是深拷貝是非常有必要的。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦