探討java深拷貝_java

來源:互聯網
上載者:User

本文將討論以下4個問題

    1. java Cloneable介面實現深拷貝
    2. java 序列化實現深拷貝
    3. 號稱最快的深拷貝二方庫cloning源碼分析
    4. 幾種拷貝方式速度的比較

深拷貝的概念本文就不說了。在C++中實現深拷貝一般情況下重載賦值操作符 “=” 來實現同一個類的對象間的深拷貝,所以很自然的在java中我們也同樣可以定義一個copy函數,在函數內部為對象的每一個屬性作賦值操作。這種方式簡單自然,但存在一個致命性的問題:如果有一天在類中新增加了一個需要深拷貝的屬性,那麼相應的copy函數也得進行修改,這種方法給類的可擴充性帶來了極大的不方便。怎麼解決這種問題,且看接下來的1、2、3章節的實現方式和4節的速度測試。
1. java Cloneable介面實現深拷貝
這種方式,需要類實現Colneable介面 clone 函數,在clone函數中調用super.clone。這種方式的深拷貝同樣會帶來另一個問題,如果類中有其他類的對象作為屬性,則其他的類也需要重載並實現Cloneable介面。來一個例子,在下例中ComplexDO中包含了SimpleDO對象,要實現ComplexDO深拷貝,則需要先實現SimpleDO的clone介面:

public class SimpleDO implements Cloneable, Serializable {    private int x = 1;    private String s = "simpleDO";    @Override    protected Object clone() throws CloneNotSupportedException {      SimpleDO newClass = (SimpleDO)super.clone();      return newClass;    }  }  public class ComplexDO implements Cloneable, Serializable {    private int x = 1;    private String s = "complex";    private Integer a = 123;    private Integer b = 1234;    private Integer c = 1334455;    private String s2 = "hehehe";    private String s3 = "hahahaha";    private Long id = 1233245L;    private ArrayList<SimpleDO> l = new ArrayList<SimpleDO>();    @Override    public Object clone() throws CloneNotSupportedException {      ComplexDO newClass = (ComplexDO) super.clone();      newClass.l = new ArrayList<SimpleDO>();      for (SimpleDO simple : this.l) {        newClass.l.add((SimpleDO) simple.clone());      }      return newClass;    }  }

需要注意的是很多文章說String類型的對象賦值操作符是深拷貝,但是其實在java中使用賦值操作符的都屬於淺拷貝,但為什麼這麼明顯的錯誤這麼多的文章會非要說這個是深拷貝呢?我的理解是String、類型的屬性都是基本類型,而且提供的方法只要是設計到內部資料的變動都會new一個新的對象出來。所以一個String的操作不會影響到其原先指向的記憶體。所以一般說String等基礎類的賦值操作為深拷貝。
由於這個原因,在使用String字串拼接的時候,需要開闢新的記憶體,所以很多人建議用StringBuilder來代替String來做拼接,因為StringBuilder只有在內建的char數組範圍不夠的時候才重新申請更大的記憶體(對於現代JVM,會對代碼調優,String+String會被最佳化成StringBuilder.append的相類似的指令)。與拼接相對的裁剪,在String有個subString函數,當使用subString函數時,新String的內部char數組和原String是否相同?這個比較有意思,感興趣的可以對比看看JDK1.6和JKD1.7的實現。
2. java 序列化實現深拷貝
這種方式的原理是利用java序列化,將一個對象序列化成二進位位元組流,然後對該位元組流還原序列化賦值給一個對象。程式碼範例:

  public Object seirCopy(Object src) {    try {      ByteArrayOutputStream byteOut = new ByteArrayOutputStream();      ObjectOutputStream out = new ObjectOutputStream(byteOut);      out.writeObject(src);      ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());      ObjectInputStream in = new ObjectInputStream(byteIn);      Object dest = in.readObject();      return dest;    } catch (Exception e) {      //do some error handler      return null;    } }

當然,也可以選用json等序列化的庫來完成序列化,這種方式有效規避了Cloneabel介面的可擴充缺點,一個函數就可以基本上適用於所有的類.缺點是相對記憶體拷貝,序列化需要先將對象轉換成二進位位元組流,然後還原序列化將該二進位位元組流重新拷貝到一塊對象記憶體,相對慢點。
3. 號稱最快的深拷貝二方庫cloning源碼分析
在源碼中,核心的處理邏輯在Cloner類中,
分兩條遞迴鏈路:

  • (1)deepClone->cloneInternal->fastClone->cloneInternal
  • (2)deepClone->cloneInternal->cloneObject->cloneInternal

在(1)中fastClone完成的是繼承自IfastCloner介面類的對象,即都是些集合操作的拷貝;
在(2)中cloneObject完成的是通過反射機制拿到普通對象的每一個屬性,然後對使用Objenesis新產生對象的屬性賦值。
這種方式可擴充性強,不僅可以依靠其現有的程式碼完成深拷貝,還可以自己定義一些複製的方式和不需要複製的類型,靈活性強。
4. 幾種拷貝方式速度的比較
上述3中模式都可以完成深拷貝,那種拷貝的方式速度最快是我們所關心的。
先上測試代碼:
 

  public void testCloneComplex() throws CloneNotSupportedException {    final int copyCount = 1;    List<ComplexDO> complexDOList = new ArrayList<ComplexDO>(copyCount * 3);    final ComplexDO complex = new ComplexDO();    //調用二方庫    long start = System.currentTimeMillis();    for(int i = 0; i < copyCount; ++i) {      final ComplexDO deepClone = cloner.deepClone(complex);      complexDOList.add(deepClone);    }    long end = System.currentTimeMillis();    System.out.println("deepClone cost time=" + (end-start));    //調用Cloneable介面實現的clone函數    start = System.currentTimeMillis();    for(int i = 0; i < copyCount; ++i) {      final ComplexDO interfaceClone = (ComplexDO) complex.clone();      complexDOList.add(interfaceClone);    }    end = System.currentTimeMillis();    System.out.println("interfaceClone cost time=" + (end-start));    //序列化與還原序列化產生新對象    start = System.currentTimeMillis();    for(int i = 0; i < copyCount; ++i) {      final ComplexDO seirClone = seirCopy(complex);      complexDOList.add(seirClone);    }    end = System.currentTimeMillis();    System.out.println("seirClone cost time=" + (end-start));  }

運行結果的單位為毫秒(此資料忽略不計算java熱點和可能的gc)。

從這個表可以得出結論:

1、實現Cloneable介面的拷貝是最快的,因為他只涉及到了記憶體拷貝,但是如果涉及的屬性為普通對象比較多的時候寫起來麻煩點
2、序列化/還原序列化拷貝最慢
3、使用cloning庫,由於使用了遞迴和反射機制相對Cloneable介面實現的拷貝要慢,但比序列化方式要快。

以上就是本文的全部內容,希望對大家的學習有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.