java對象clone()方法


java對象clone()方法

java賦值是復制對象引用,如果我們想要得到一個對象的副本,使用賦值操作是無法達到目的的:

  1.  
    @Test
  2.  
    public void testassign(){
  3.  
    Person p1= new Person();
  4.  
    p1.setAge( 31);
  5.  
    p1.setName( "Peter");
  6.  
     
  7.  
    Person p2=p1;
  8.  
    System.out.println(p1==p2); //true
  9.  
    }

如果創建一個對象的新的副本,也就是說他們的初始狀態完全一樣,但以后可以改變各自的狀態,而互不影響,就需要用到java中對象的復制,如原生的clone()方法。

如何進行對象克隆

Object對象有個clone()方法,實現了對象中各個屬性的復制,但它的可見范圍是protected的,所以實體類使用克隆的前提是:

① 實現Cloneable接口,這是一個標記接口,自身沒有方法。 
② 覆蓋clone()方法,可見性提升為public。

  1.  
    @Data
  2.  
    public class Person implements Cloneable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    return super.clone();
  9.  
    }
  10.  
    }
  11.  
     
  12.  
    @Test
  13.  
    public void testShallowCopy() throws Exception{
  14.  
    Person p1= new Person();
  15.  
    p1.setAge( 31);
  16.  
    p1.setName( "Peter");
  17.  
     
  18.  
    Person p2=(Person) p1.clone();
  19.  
    System.out.println(p1==p2); //false
  20.  
    p2.setName( "Jacky");
  21.  
    System.out.println( "p1="+p1);//p1=Person [name=Peter, age=31]
  22.  
    System.out.println( "p2="+p2);//p2=Person [name=Jacky, age=31]
  23.  
    }

該測試用例只有兩個基本類型的成員,測試達到目的了。

事情貌似沒有這么簡單,為Person增加一個Address類的成員:

  1.  
    @Data
  2.  
    public class Address {
  3.  
    private String type;
  4.  
    private String value;
  5.  
    }

再來測試,問題來了。

  1.  
    @Test
  2.  
    public void testShallowCopy() throws Exception{
  3.  
    Address address= new Address();
  4.  
    address.setType( "Home");
  5.  
    address.setValue( "北京");
  6.  
     
  7.  
    Person p1= new Person();
  8.  
    p1.setAge( 31);
  9.  
    p1.setName( "Peter");
  10.  
    p1.setAddress(address);
  11.  
     
  12.  
    Person p2=(Person) p1.clone();
  13.  
    System.out.println(p1==p2); //false
  14.  
     
  15.  
    p2.getAddress().setType( "Office");
  16.  
    System.out.println( "p1="+p1);
  17.  
    System.out.println( "p2="+p2);
  18.  
    }

查看輸出:

  1.  
    false
  2.  
    p1=Person( name=Peter, age=31, address=Address(type=Office, value=北京))
  3.  
    p2=Person( name=Peter, age=31, address=Address(type=Office, value=北京))

遇到了點麻煩,只修改了p2的地址類型,兩個地址類型都變成了Office。

淺拷貝和深拷貝

前面實例中是淺拷貝和深拷貝的典型用例。

淺拷貝:被復制對象的所有值屬性都含有與原來對象的相同,而所有的對象引用屬性仍然指向原來的對象。

深拷貝:在淺拷貝的基礎上,所有引用其他對象的變量也進行了clone,並指向被復制過的新對象。

也就是說,一個默認的clone()方法實現機制,仍然是賦值。

如果一個被復制的屬性都是基本類型,那么只需要實現當前類的cloneable機制就可以了,此為淺拷貝。

如果被復制對象的屬性包含其他實體類對象引用,那么這些實體類對象都需要實現cloneable接口並覆蓋clone()方法。

  1.  
    @Data
  2.  
    public class Address implements Cloneable {
  3.  
    private String type;
  4.  
    private String value;
  5.  
     
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    return super.clone();
  9.  
    }
  10.  
    }

這樣還不夠,Person的clone()需要顯式地clone其引用成員。

  1.  
    @Data
  2.  
    public class Person implements Cloneable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    Object obj= super.clone();
  9.  
    Address a=((Person)obj).getAddress();
  10.  
    ((Person)obj).setAddress((Address) a.clone());
  11.  
    return obj;
  12.  
    }
  13.  
    }

重新跑前面的測試用例:

  1.  
    false
  2.  
    p1=Person( name=Peter, age=31, address=Address(type=Home, value=北京))
  3.  
    p2=Person( name=Peter, age=31, address=Address(type=Office, value=北京))

clone方式深拷貝小結

① 如果有一個非原生成員,如自定義對象的成員,那么就需要:

  • 該成員實現Cloneable接口並覆蓋clone()方法,不要忘記提升為public可見。
  • 同時,修改被復制類的clone()方法,增加成員的克隆邏輯。

② 如果被復制對象不是直接繼承Object,中間還有其它繼承層次,每一層super類都需要實現Cloneable接口並覆蓋clone()方法。

與對象成員不同,繼承關系中的clone不需要被復制類的clone()做多余的工作。

一句話來說,如果實現完整的深拷貝,需要被復制對象的繼承鏈、引用鏈上的每一個對象都實現克隆機制。

前面的實例還可以接受,如果有N個對象成員,有M層繼承關系,就會很麻煩。

利用序列化實現深拷貝

clone機制不是強類型的限制,比如實現了Cloneable並沒有強制繼承鏈上的對象也實現;也沒有強制要求覆蓋clone()方法。因此編碼過程中比較容易忽略其中一個環節,對於復雜的項目排查就是困難了。

要尋找可靠的,簡單的方法,序列化就是一種途徑。

  • 被復制對象的繼承鏈、引用鏈上的每一個對象都實現java.io.Serializable接口。這個比較簡單,不需要實現任何方法,serialVersionID的要求不強制,對深拷貝來說沒毛病。

  • 實現自己的deepClone方法,將this寫入流,再讀出來。俗稱:冷凍-解凍。

  1.  
    @Data
  2.  
    public class Person implements Serializable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    public Person deepClone() {
  7.  
    Person p2= null;
  8.  
    Person p1= this;
  9.  
    PipedOutputStream out= new PipedOutputStream();
  10.  
    PipedInputStream in= new PipedInputStream();
  11.  
    try {
  12.  
    in.connect(out);
  13.  
    } catch (IOException e) {
  14.  
    e.printStackTrace();
  15.  
    }
  16.  
     
  17.  
    try(ObjectOutputStream bo=new ObjectOutputStream(out);
  18.  
    ObjectInputStream bi= new ObjectInputStream(in);) {
  19.  
    bo.writeObject(p1);
  20.  
    p2=(Person) bi.readObject();
  21.  
     
  22.  
    } catch (Exception e) {
  23.  
    e.printStackTrace();
  24.  
    }
  25.  
    return p2;
  26.  
    }
  27.  
    }

原型工廠類

為了便於測試,也節省篇幅,封裝一個工廠類。

公平起見,避免某些工具庫使用緩存機制,使用原型方式工廠。

  1.  
    public class PersonFactory{
  2.  
    public static Person newPrototypeInstance(){
  3.  
    Address address = new Address();
  4.  
    address.setType( "Home");
  5.  
    address.setValue( "北京");
  6.  
     
  7.  
    Person p1 = new Person();
  8.  
    p1.setAddress(address);
  9.  
    p1.setAge( 31);
  10.  
    p1.setName( "Peter");
  11.  
    return p1;
  12.  
    }
  13.  
    }

利用Dozer拷貝對象

Dozer是一個Bean處理類庫。

maven依賴

  1.  
    <dependency>
  2.  
    <groupId>net.sf.dozer</groupId>
  3.  
    <artifactId>dozer</artifactId>
  4.  
    <version>5.5.1</version>
  5.  
    </dependency>

測試用例:

  1.  
    @Data
  2.  
    public class Person {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    Person p2 = mapper.map(p1, Person.class);
  12.  
    p2.getAddress().setType( "Office");
  13.  
    System.out.println( "p1=" + p1);
  14.  
    System.out.println( "p2=" + p2);
  15.  
    }
  16.  
    }
  17.  
     
  18.  
    @Data
  19.  
    public class Address {
  20.  
    private String type;
  21.  
    private String value;
  22.  
    }

輸出:

  1.  
    p1=Person( name=Peter, age=31, address=Address(type=Home, value=北京))
  2.  
    p2=Person( name=Peter, age=31, address=Address(type=Office, value=北京))

注意:在萬次測試中dozer有一個很嚴重的問題,如果DozerBeanMapper對象在for循環中創建,效率(dozer:7358)降低近10倍。由於DozerBeanMapper是線程安全的,所以不應該每次都創建新的實例。可以自帶的單例工廠DozerBeanMapperSingletonWrapper來創建mapper,或集成到spring中。

還有更暴力的,創建一個People類:

  1.  
    @Data
  2.  
    public class People {
  3.  
    private String name;
  4.  
    private String age;//這里已經不是Integer了
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    People p2 = mapper.map(p1, People.class);
  12.  
    p2.getAddress().setType( "Office");
  13.  
    System.out.println( "p1=" + p1);
  14.  
    System.out.println( "p2=" + p2);
  15.  
    }
  16.  
    }

只要屬性名相同,干~

繼續蹂躪:

  1.  
    @Data
  2.  
    public class People {
  3.  
    private String name;
  4.  
    private String age;
  5.  
    private Map<String,String> address;//��
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    People p2 = mapper.map(p1, People.class);
  12.  
    p2.getAddress().put( "type", "Office");
  13.  
    System.out.println( "p1=" + p1);
  14.  
    System.out.println( "p2=" + p2);
  15.  
    }
  16.  
    }

利用Commons-BeanUtils復制對象

maven依賴

  1.  
    <dependency>
  2.  
    <groupId>commons-beanutils</groupId>
  3.  
    <artifactId>commons-beanutils</artifactId>
  4.  
    <version>1.9.3</version>
  5.  
    </dependency>

測試用例:

  1.  
    @Data
  2.  
    public class Person {
  3.  
    private String name;
  4.  
    private String age;
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testCommonsBeanUtils(){
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    try {
  11.  
    Person p2=(Person) BeanUtils.cloneBean(p1);
  12.  
    System.out.println( "p1=" + p1);
  13.  
    p2.getAddress().setType( "Office");
  14.  
    System.out.println( "p2=" + p2);
  15.  
    } catch (Exception e) {
  16.  
    e.printStackTrace();
  17.  
    }
  18.  
    }
  19.  
    }

利用cglib復制對象

maven依賴:

  1.  
    <dependency>
  2.  
    <groupId>cglib</groupId>
  3.  
    <artifactId>cglib</artifactId>
  4.  
    <version>3.2.4</version>
  5.  
    </dependency>

測試用例:

  1.  
    @Test
  2.  
    public void testCglib(){
  3.  
    Person p1=PersonFactory.newPrototypeInstance();
  4.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
  5.  
    Person p2= new Person();
  6.  
    beanCopier.copy(p1, p2, null);
  7.  
    p2.getAddress().setType( "Office");
  8.  
    System.out.println( "p1=" + p1);
  9.  
    System.out.println( "p2=" + p2);
  10.  
    }

結果大跌眼鏡,cglib這么牛x,居然是淺拷貝。不過cglib提供了擴展能力:

  1.  
    @Test
  2.  
    public void testCglib(){
  3.  
    Person p1=PersonFactory.newPrototypeInstance();
  4.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  5.  
    Person p2= new Person();
  6.  
    beanCopier.copy(p1, p2, new Converter(){
  7.  
    @Override
  8.  
    public Object convert(Object value, Class target, Object context) {
  9.  
    if(target.isSynthetic()){
  10.  
    BeanCopier.create(target, target, true).copy(value, value, this);
  11.  
    }
  12.  
    return value;
  13.  
    }
  14.  
    });
  15.  
    p2.getAddress().setType( "Office");
  16.  
    System.out.println( "p1=" + p1);
  17.  
    System.out.println( "p2=" + p2);
  18.  
    }

Orika復制對象

orika的作用不僅僅在於處理bean拷貝,更擅長各種類型之間的轉換。

maven依賴:

  1.  
    <dependency>
  2.  
    <groupId>ma.glasnost.orika</groupId>
  3.  
    <artifactId>orika-core</artifactId>
  4.  
    <version>1.5.0</version>
  5.  
    </dependency>
  6.  
    </dependencies>

測試用例:

  1.  
    @Test
  2.  
    public void testOrika() {
  3.  
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
  4.  
     
  5.  
    mapperFactory.classMap(Person.class, Person.class)
  6.  
    .byDefault()
  7.  
    .register();
  8.  
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  9.  
    MapperFacade mapper = mapperFactory.getMapperFacade();
  10.  
     
  11.  
    Person p1=PersonFactory.newPrototypeInstance();
  12.  
    Person p2 = mapper.map(p1, Person.class);
  13.  
    System.out.println( "p1=" + p1);
  14.  
    p2.getAddress().setType( "Office");
  15.  
    System.out.println( "p2=" + p2);
  16.  
    }

Spring BeanUtils復制對象

給Spring個面子,貌似它不支持深拷貝。

  1.  
    Person p1=PersonFactory.newPrototypeInstance();
  2.  
    Person p2 = new Person();
  3.  
    Person p2 = (Person) BeanUtils.cloneBean(p1);
  4.  
    //BeanUtils.copyProperties(p2, p1);//這個更沒戲

深拷貝性能對比

  1.  
    @Test
  2.  
    public void testBatchDozer(){
  3.  
    Long start=System.currentTimeMillis();
  4.  
    Mapper mapper = new DozerBeanMapper();
  5.  
    for(int i=0;i<10000;i++){
  6.  
    Person p1=PersonFactory.newPrototypeInstance();
  7.  
    Person p2 = mapper.map(p1, Person.class);
  8.  
    }
  9.  
    System.out.println( "dozer:"+(System.currentTimeMillis()-start));
  10.  
    //dozer:721
  11.  
    }
  12.  
    @Test
  13.  
    public void testBatchBeanUtils(){
  14.  
    Long start=System.currentTimeMillis();
  15.  
    for(int i=0;i<10000;i++){
  16.  
    Person p1=PersonFactory.newPrototypeInstance();
  17.  
    try {
  18.  
    Person p2=(Person) BeanUtils.cloneBean(p1);
  19.  
    } catch (Exception e) {
  20.  
    e.printStackTrace();
  21.  
    }
  22.  
    }
  23.  
    System.out.println( "commons-beanutils:"+(System.currentTimeMillis()-start));
  24.  
    //commons-beanutils:229
  25.  
    }
  26.  
    @Test
  27.  
    public void testBatchCglib(){
  28.  
    Long start=System.currentTimeMillis();
  29.  
    for(int i=0;i<10000;i++){
  30.  
    Person p1=PersonFactory.newPrototypeInstance();
  31.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  32.  
    Person p2= new Person();
  33.  
    beanCopier.copy(p1, p2, new Converter(){
  34.  
    @Override
  35.  
    public Object convert(Object value, Class target, Object context) {
  36.  
    if(target.isSynthetic()){
  37.  
    BeanCopier.create(target, target, true).copy(value, value, this);
  38.  
    }
  39.  
    return value;
  40.  
    }
  41.  
    });
  42.  
    }
  43.  
    System.out.println( "cglib:"+(System.currentTimeMillis()-start));
  44.  
    //cglib:133
  45.  
    }
  46.  
    @Test
  47.  
    public void testBatchSerial(){
  48.  
    Long start=System.currentTimeMillis();
  49.  
    for(int i=0;i<10000;i++){
  50.  
    Person p1=PersonFactory.newPrototypeInstance();
  51.  
    Person p2=p1.deepClone();
  52.  
    }
  53.  
    System.out.println( "serializable:"+(System.currentTimeMillis()-start));
  54.  
    //serializable:687
  55.  
    }
  56.  
    @Test
  57.  
    public void testBatchOrika() {
  58.  
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
  59.  
     
  60.  
    mapperFactory.classMap(Person.class, Person.class)
  61.  
    .field( "name", "name")
  62.  
    .byDefault()
  63.  
    .register();
  64.  
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  65.  
    MapperFacade mapper = mapperFactory.getMapperFacade();
  66.  
     
  67.  
    Long start=System.currentTimeMillis();
  68.  
    for(int i=0;i<10000;i++){
  69.  
    Person p1=PersonFactory.newPrototypeInstance();
  70.  
    Person p2 = mapper.map(p1, Person.class);
  71.  
    }
  72.  
    System.out.println( "orika:"+(System.currentTimeMillis()-start));
  73.  
    //orika:83
  74.  
    }
  75.  
     
  76.  
    @Test
  77.  
    public void testBatchClone(){
  78.  
    Long start=System.currentTimeMillis();
  79.  
    for(int i=0;i<10000;i++){
  80.  
    Person p1=PersonFactory.newPrototypeInstance();
  81.  
    try {
  82.  
    Person p2=(Person) p1.clone();
  83.  
    } catch (CloneNotSupportedException e) {
  84.  
    e.printStackTrace();
  85.  
    }
  86.  
    }
  87.  
    System.out.println( "clone:"+(System.currentTimeMillis()-start));
  88.  
    //clone:8
  89.  
    }

(10k)性能比較:

  1.  
    //dozer:721
  2.  
    //commons-beanutils:229
  3.  
    //cglib:133
  4.  
    //serializable:687
  5.  
    //orika:83
  6.  
    //clone:8

深拷貝總結

原生的clone效率無疑是最高的,用腳趾頭都能想到。

偶爾用一次,用哪個都問題都不大。

一般性能要求稍高的應用場景,cglib和orika完全可以接受。

另外一個考慮的因素,如果項目已經引入了某個依賴,就用那個依賴來做吧,沒必要再引入一個第三方依賴。

轉載自https://blog.csdn.net/54powerman/article/details/64920431?locationNum=6&fps=1

有想學雲計算的朋友可以看看這篇https://blog.csdn.net/qq_33314107/article/details/90677795


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM