java object copy

Keywords: Maven Java Spring Attribute

java assignment is to copy object references. If we want to get a copy of an object, using assignment operation can not achieve the goal:

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

  Person p2=p1;
  System.out.println(p1==p2);//true
}

If you create a new copy of an object, that is to say, their initial states are exactly the same, but they can change their respective states later without affecting each other, you need to use the object replication in java, such as the native clone() method.

How to Clone Objects

Object object has a clone() method to replicate the attributes in the object, but its visible range is protected, so the premise of using clone for entity class is:

(1) Implementing Cloneable interface, which is a markup interface, has no method of its own. (2) Overlay clone() method and increase visibility to public.

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

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

  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]
}

The test case has only two basic types of members, and the test achieves its purpose.

It doesn't seem so easy to add a member of the Address class to Person:

@Data
public class Address {
    private String type;
    private String value;
}

Test again, the problem is coming.

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

  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);
}

View output:

false
p1=Person(name=Peter, age=31, address=Address(type=Office, value=Beijing))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=Beijing))

A little trouble was encountered, only the address type of p2 was changed, and both address types became Office.

Shallow and deep copies

The previous examples are typical use cases of shallow and deep copies.

Shallow copy: All value attributes of the duplicated object contain the same attributes as the original object, while all object reference attributes still point to the original object.

Deep copy: On the basis of shallow copy, all variables that refer to other objects are clone d and pointed to new objects that have been copied.

That is to say, a default clone() method implementation mechanism is still assignment.

If a duplicated attribute is a basic type, then you only need to implement the cloneable mechanism of the current class, which is a shallow copy.

If the attributes of the replicated object contain references to other entity class objects, then these entity class objects need to implement the cloneable interface and override the clone() method.

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

That's not enough. Person's clone() needs to explicitly clone its reference members.

@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;
    }
}

Rerun the previous test case:

false
p1=Person(name=Peter, age=31, address=Address(type=Home, value=Beijing))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=Beijing))

clone mode deep copy summary

If you have a non-native member, such as a member of a custom object, you need to:

  • This member implements the Cloneable interface and overrides the clone() method. Don't forget to promote it to public visibility.
  • At the same time, the clone() method of the duplicated class is modified to increase the clone logic of the members.

(2) If the duplicated object does not inherit Object directly, there are other inheritance levels in it. Each super class needs to implement Cloneable interface and override clone() method.

Unlike object members, clone in inheritance does not need clone() of the duplicated class to do superfluous work.

In a word, if a complete deep copy is implemented, the cloning mechanism needs to be implemented for each object in the inheritance chain and reference chain of the duplicated object.

The previous example is also acceptable. If there are N object members and M-layer inheritance, it will be very troublesome.

Using serialization to achieve deep copy

Clone mechanism is not a strongly typed limitation, such as implementing Cloneable without forcing objects on the inheritance chain to be implemented, nor forcing override of clone() methods. Therefore, it is easy to overlook one of the links in the coding process, and it is difficult for complex project checking.

Serialization is one way to find reliable and simple methods.

  • Every object in the inheritance chain and reference chain of the duplicated object implements the java.io.Serializable interface. This is relatively simple, does not need to implement any method, serialVersionID requirements are not mandatory, for deep copy is not a problem.

  • Implement your own deep Clone method, write this to the stream and read it out. Commonly known as: freezing-thawing.
@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;
    }
}

Prototype factory class

To facilitate testing and save space, encapsulate a factory class.

To be fair, avoid some tool libraries using caching mechanisms and prototype factories.

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

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

Using Dozer to Copy Objects

Dozer is a Bean processing class library.

maven dependence

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.5.1</version>
</dependency>

Test cases:

@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;
}

Output:

p1=Person(name=Peter, age=31, address=Address(type=Home, value=Beijing))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=Beijing))

Note: Dozer has a serious problem in 10,000 tests. If the DozerBeanMapper object is created in the for loop, the efficiency (dozer:7358) decreases by nearly 10 times. Because DozerBeanMapper is thread-safe, new instances should not be created every time. DozerBean Mapper Singleton Wrapper can be brought with it to create mapper or integrate into spring.

More violent, create a People class:

@Data
public class People {
    private String name;
    private String age;//This is no longer 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);
    }
}

As long as the attribute name is the same, do~

Continue to ravage:

@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);
    }
}

Replicating Objects with Commons-BeanUtils

maven dependence

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.9.3</version>
</dependency>

Test cases:

@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();
        }
    }
}

Copying objects with cglib

maven dependence:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.4</version>
</dependency>

Test cases:

@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);
}

As a result, cglib is a superficial copy. However, cglib provides scalability:

@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);
}

Orika replicates objects

orika is not only good at handling bean copies, but also good at transforming between different types.

maven dependence:

<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.0</version>
</dependency>
</dependencies>

Test cases:

@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);
}

Spring BeanUtils replicates objects

Give Spring face. It doesn't seem to support deep copy.

Person p1=PersonFactory.newPrototypeInstance();
Person p2 = new Person();
Person p2 = (Person) BeanUtils.cloneBean(p1);
//BeanUtils.copyProperties(p2, p1); // This is even worse.

Performance comparison of deep copy

@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
}

(10k) Performance comparison:

//dozer:721
//commons-beanutils: 229
//cglib:133
//serializable:687
//orika:83
//clone:8

Deep copy summary

Native clone efficiency is undoubtedly the highest, with toes can be imagined.

Once in a while, it's not a big problem to use either.

cglib and orika are perfectly acceptable for application scenarios with slightly higher performance requirements.

Another consideration is that if a project has introduced a dependency, use that dependency instead of introducing a third-party dependency.

Posted by aneesme on Tue, 16 Jul 2019 14:57:45 -0700