java對象拷貝最徹底解說

java賦值是複製對象引用,若是咱們想要獲得一個對象的副本,使用賦值操做是沒法達到目的的:java

@Test
public void testassign(){
  Person p1=new Person();
  p1.setAge(31);
  p1.setName("Peter");spring

  Person p2=p1;
  System.out.println(p1==p2);//true
}
1
2
3
4
5
6
7
8
9
若是建立一個對象的新的副本,也就是說他們的初始狀態徹底同樣,但之後能夠改變各自的狀態,而互不影響,就須要用到java中對象的複製,如原生的clone()方法。緩存

如何進行對象克隆
Object對象有個clone()方法,實現了對象中各個屬性的複製,但它的可見範圍是protected的,因此實體類使用克隆的前提是:安全

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

@Data
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Address address;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}maven

@Test
public void testShallowCopy() throws Exception{
  Person p1=new Person();
  p1.setAge(31);
  p1.setName("Peter");ide

  Person p2=(Person) p1.clone();
  System.out.println(p1==p2);//false
  p2.setName("Jacky");
  System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
  System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
該測試用例只有兩個基本類型的成員,測試達到目的了。工具

事情貌似沒有這麼簡單,爲Person增長一個Address類的成員:性能

@Data
public class Address {
    private String type;
    private String value;
}
1
2
3
4
5
再來測試,問題來了。測試

@Test
public void testShallowCopy() throws Exception{
  Address address=new Address();
  address.setType("Home");
  address.setValue("北京");

  Person p1=new Person();
  p1.setAge(31);
  p1.setName("Peter");
  p1.setAddress(address);

  Person p2=(Person) p1.clone();
  System.out.println(p1==p2);//false

  p2.getAddress().setType("Office");
  System.out.println("p1="+p1);
  System.out.println("p2="+p2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
查看輸出:

false
p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
1
2
3
遇到了點麻煩,只修改了p2的地址類型,兩個地址類型都變成了Office。

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

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

深拷貝:在淺拷貝的基礎上,全部引用其餘對象的變量也進行了clone,並指向被複制過的新對象。

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

若是一個被複制的屬性都是基本類型,那麼只須要實現當前類的cloneable機制就能夠了,此爲淺拷貝。

若是被複制對象的屬性包含其餘實體類對象引用,那麼這些實體類對象都須要實現cloneable接口並覆蓋clone()方法。

@Data
public class Address implements Cloneable {
    private String type;
    private String value;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
1
2
3
4
5
6
7
8
9
10
這樣還不夠,Person的clone()須要顯式地clone其引用成員。

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

false
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
1
2
3
clone方式深拷貝小結
① 若是有一個非原生成員,如自定義對象的成員,那麼就須要:

該成員實現Cloneable接口並覆蓋clone()方法,不要忘記提高爲public可見。
同時,修改被複制類的clone()方法,增長成員的克隆邏輯。
② 若是被複制對象不是直接繼承Object,中間還有其它繼承層次,每一層super類都須要實現Cloneable接口並覆蓋clone()方法。

與對象成員不一樣,繼承關係中的clone不須要被複制類的clone()作多餘的工做。

一句話來講,若是實現完整的深拷貝,須要被複制對象的繼承鏈、引用鏈上的每個對象都實現克隆機制。

前面的實例還能夠接受,若是有N個對象成員,有M層繼承關係,就會很麻煩。

利用序列化實現深拷貝
clone機制不是強類型的限制,好比實現了Cloneable並無強制繼承鏈上的對象也實現;也沒有強制要求覆蓋clone()方法。所以編碼過程當中比較容易忽略其中一個環節,對於複雜的項目排查就是困難了。

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

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

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

@Data
public class Person implements Serializable {
    private String name;
    private Integer age;
    private Address address;
    public Person deepClone() {
        Person p2=null;
        Person p1=this;
        PipedOutputStream out=new PipedOutputStream();
        PipedInputStream in=new PipedInputStream();
        try {
            in.connect(out);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try(ObjectOutputStream bo=new ObjectOutputStream(out);
                ObjectInputStream bi=new ObjectInputStream(in);) {
            bo.writeObject(p1);
            p2=(Person) bi.readObject();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return p2;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
原型工廠類
爲了便於測試,也節省篇幅,封裝一個工廠類。

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

public class PersonFactory{
    public static Person newPrototypeInstance(){
        Address address = new Address();
        address.setType("Home");
        address.setValue("北京");

        Person p1 = new Person();
        p1.setAddress(address);
        p1.setAge(31);
        p1.setName("Peter");
        return p1;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
利用Dozer拷貝對象
Dozer是一個Bean處理類庫。

maven依賴

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>
1
2
3
4
5
測試用例:

@Data
public class Person {
    private String name;
    private Integer age;
    private Address address;

    @Test
    public void testDozer() {
    Person p1=PersonFactory.newPrototypeInstance();
        Mapper mapper = new DozerBeanMapper();
        Person p2 = mapper.map(p1, Person.class);
        p2.getAddress().setType("Office");
        System.out.println("p1=" + p1);
        System.out.println("p2=" + p2);
    }
}

@Data
public class Address {
    private String type;
    private String value;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
輸出:

p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
1
2
注意:在萬次測試中dozer有一個很嚴重的問題,若是DozerBeanMapper對象在for循環中建立,效率(dozer:7358)下降近10倍。因爲DozerBeanMapper是線程安全的,因此不該該每次都建立新的實例。能夠自帶的單例工廠DozerBeanMapperSingletonWrapper來建立mapper,或集成到spring中。

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

@Data
public class People {
    private String name;
    private String age;//這裏已經不是Integer了
    private Address address;

    @Test
    public void testDozer() {
    Person p1=PersonFactory.newPrototypeInstance();
        Mapper mapper = new DozerBeanMapper();
        People p2 = mapper.map(p1, People.class);
        p2.getAddress().setType("Office");
        System.out.println("p1=" + p1);
        System.out.println("p2=" + p2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
只要屬性名相同,幹~

繼續蹂躪:

@Data
public class People {
    private String name;
    private String age;
    private Map<String,String> address;//��

    @Test
    public void testDozer() {
    Person p1=PersonFactory.newPrototypeInstance();
        Mapper mapper = new DozerBeanMapper();
        People p2 = mapper.map(p1, People.class);
        p2.getAddress().put("type", "Office");
        System.out.println("p1=" + p1);
        System.out.println("p2=" + p2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
利用Commons-BeanUtils複製對象
maven依賴

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.9.3</version>
</dependency>
1
2
3
4
5
測試用例:

@Data
public class Person {
    private String name;
    private String age;
    private Address address;

    @Test
    public void testCommonsBeanUtils(){
    Person p1=PersonFactory.newPrototypeInstance();
        try {
            Person p2=(Person) BeanUtils.cloneBean(p1);
            System.out.println("p1=" + p1);
            p2.getAddress().setType("Office");
            System.out.println("p2=" + p2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
利用cglib複製對象
maven依賴:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.4</version>
</dependency>
1
2
3
4
5
測試用例:

@Test
public void testCglib(){
  Person p1=PersonFactory.newPrototypeInstance();
  BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
  Person p2=new Person();
  beanCopier.copy(p1, p2,null);
  p2.getAddress().setType("Office");
  System.out.println("p1=" + p1);
  System.out.println("p2=" + p2);
}
1
2
3
4
5
6
7
8
9
10
結果大跌眼鏡,cglib這麼牛x,竟然是淺拷貝。不過cglib提供了擴展能力:

@Test
public void testCglib(){
  Person p1=PersonFactory.newPrototypeInstance();
  BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  Person p2=new Person();
  beanCopier.copy(p1, p2, new Converter(){
    @Override
    public Object convert(Object value, Class target, Object context) {
      if(target.isSynthetic()){
        BeanCopier.create(target, target, true).copy(value, value, this);
      }
      return value;
    }
  });
  p2.getAddress().setType("Office");
  System.out.println("p1=" + p1);
  System.out.println("p2=" + p2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Orika複製對象
orika的做用不只僅在於處理bean拷貝,更擅長各類類型之間的轉換。

maven依賴:

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

@Test
public void testOrika() {
  MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

  mapperFactory.classMap(Person.class, Person.class)
  .byDefault()
  .register();
  ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  MapperFacade mapper = mapperFactory.getMapperFacade();

  Person p1=PersonFactory.newPrototypeInstance();
  Person p2 = mapper.map(p1, Person.class);
  System.out.println("p1=" + p1);
  p2.getAddress().setType("Office");
  System.out.println("p2=" + p2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Spring BeanUtils複製對象
給Spring個面子,貌似它不支持深拷貝。

Person p1=PersonFactory.newPrototypeInstance();
Person p2 = new Person();
Person p2 = (Person) BeanUtils.cloneBean(p1);
//BeanUtils.copyProperties(p2, p1);//這個更沒戲
1
2
3
4
深拷貝性能對比
@Test
public void testBatchDozer(){
  Long start=System.currentTimeMillis();
  Mapper mapper = new DozerBeanMapper();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    Person p2 = mapper.map(p1, Person.class);
  }
  System.out.println("dozer:"+(System.currentTimeMillis()-start));
  //dozer:721
}
@Test
public void testBatchBeanUtils(){
  Long start=System.currentTimeMillis();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    try {
      Person p2=(Person) BeanUtils.cloneBean(p1);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start));
  //commons-beanutils:229
}
@Test
public void testBatchCglib(){
  Long start=System.currentTimeMillis();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
    Person p2=new Person();
    beanCopier.copy(p1, p2, new Converter(){
      @Override
      public Object convert(Object value, Class target, Object context) {
        if(target.isSynthetic()){
          BeanCopier.create(target, target, true).copy(value, value, this);
        }
        return value;
      }
    });
  }
  System.out.println("cglib:"+(System.currentTimeMillis()-start));
  //cglib:133
}
@Test
public void testBatchSerial(){
  Long start=System.currentTimeMillis();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    Person p2=p1.deepClone();
  }
  System.out.println("serializable:"+(System.currentTimeMillis()-start));
  //serializable:687
}
@Test
public void testBatchOrika() {
  MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

  mapperFactory.classMap(Person.class, Person.class)
  .field("name", "name")
  .byDefault()
  .register();
  ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  MapperFacade mapper = mapperFactory.getMapperFacade();

  Long start=System.currentTimeMillis();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    Person p2 = mapper.map(p1, Person.class);
  }
  System.out.println("orika:"+(System.currentTimeMillis()-start));
  //orika:83
}

@Test
public void testBatchClone(){
  Long start=System.currentTimeMillis();
  for(int i=0;i<10000;i++){
    Person p1=PersonFactory.newPrototypeInstance();
    try {
      Person p2=(Person) p1.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
  }
  System.out.println("clone:"+(System.currentTimeMillis()-start));
  //clone:8
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
(10k)性能比較:

//dozer:721
//commons-beanutils:229
//cglib:133
//serializable:687
//orika:83
//clone:8
1
2
3
4
5
6
深拷貝總結
原生的clone效率無疑是最高的,用腳趾頭都能想到。

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

通常性能要求稍高的應用場景,cglib和orika徹底能夠接受。

相關文章
相關標籤/搜索