How can we create a deep copy of an object in Java - Java @ Desk

Tuesday, November 25, 2014

How can we create a deep copy of an object in Java

How can we create a deep copy of an object in Java?

Copying objects in Java

There are two ways of creating copies of objects (and arrays) in Java:
1. Shallow Copy
2. Deep Copy

What is "Deep Copy"?

A deep copy copies all fields, and makes copies of inner objects as well (unlike creating a copy of the memory address of the inner object). A deep copy occurs when an object, along with all the objects to which it refers, is copied as a whole. After a deep copy, if we change the property of the inner object in the copied object, it will not affect the respective property of the inner object in the original object.

How can we create a deep copy of an object?

We can achieve deep copy in 3 ways.
1. Serialize and de-serialize
2. Clone the Object correctly
3. Copy Constructor

If the object which we need to deep copy is simple, we can go for either the copy constructor approach or we can implement the clone correctly. But in most cases we will need to deep copy various objects of various complexities. The object graph may be much more complex which results in increased complexity of both the copy constructor and the clone methods. In this case we can go for the serialization approach. By using serialization, we can create deep copies of any object s that adhere to a set of rules by just one function.

Rules for deep copy using serialization to work correctly
1. All classes in the object graph should be serializable
2. Serialization should not be overridden such that new instances are not created, e.g. for singletons.

How to implement serialization?
We need to use a common utility class and provide a static method that will serialize and de-serialize the object, thus creating a deep copy of that object, and then return the copy. Serialization and deserialization of the object ensures that all the classes in the object graph are recreated in a new heap location.

Sample code illustrating deep copy through serialization/deserialization:

/**
 * Util Class
 *
 */
class CommonUtil{
 
 /**
  * Method to provide deep copy
  * @param obj - Object to be deep copied
  * @return Deep copied Object
  * @throws IOException
  * @throws ClassNotFoundException
  */
 public static <T extends Object> T getCopy(T obj) throws IOException, ClassNotFoundException{
  
  ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
  ObjectOutputStream objectOutStream = new ObjectOutputStream                                 (byteOutStream);
  objectOutStream.writeObject(obj);
  objectOutStream.flush();
  objectOutStream.close();
  byteOutStream.close();
  byte[] byteData = byteOutStream.toByteArray();

  ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
  T copy = (T) new ObjectInputStream(bais).readObject();
  
  return copy;
 }
}




What is "Shallow Copy"?


When we clone an object, only the values of that object get copied. The newly created object has an exact copy of the values in the original object. If any of the fields of the original object store references to other objects, then, just the reference addresses are copied (i.e., only the memory address is copied). So, manipulating the property of the inner object in a copied object after a shallow copy will result in the modification of the respective property of the inner object in the original object as well, as both points to the same address in the heap.

Sample code (with output) illustrating shallow copy:

class School implements Serializable, Cloneable {
 
 private String name;
 private Address address;
 
 public void setAddress(Address address){
  this.address = address;
 }
 
 public Address getAddress(){
  return address;
 }
 
 public void setName(String name){
  this.name = name;
 }
 
 public String getName(){
  return name;
 }
 
 @Override
 public String toString(){
  StringBuilder builder = new StringBuilder();
  builder.append("School Name: ");
  builder.append(name);
  builder.append(", ");
  builder.append("Address: ");
  builder.append(address.getAddress());
  return builder.toString();
 }
 
 @Override
 protected Object clone() throws CloneNotSupportedException {
  return super.clone();
 }
 
}


class Address implements Serializable, Cloneable{
 
 private String address;
 
 public Address(){
  
 }
 
 public Address(String name){
  this.address = name;
 }
 
 public String getAddress(){
  return address;
 }
 
 public void setAddress(String address){
  this.address = address;
 }
 
 @Override
 protected Object clone() throws CloneNotSupportedException {
  return super.clone();
 }
}

//Code to run application

School s1 = new School();
s1.setName("General School");
s1.setAddress(new Address("School 1 Address"));

School s2 = (School) s1.clone();// Deep copy by improper clone implementation
s2.getAddress().setAddress("School 2 Address");
  
System.out.println(s1);
System.out.println(s2);

Output:
School Name: General School, Address: School 2 Address
School Name: General School, Address: School 2 Address


Note here, that the change made to the value of "address" in the cloned object (s2) changed the value of "address" in the original object (s1) as well, as they both point to same object in the heap.



How Deep Copy can be implemented effectively in the above example:

1. Override clone Correctly
In the above case if we override the clone method in the School correctly we can create a deep copy.

@Override
protected Object clone() throws CloneNotSupportedException {
  
School clone = new School();
clone.setName(name);
clone.setAddress((Address)address.clone());
return clone;
}

2. Copy Constructor
We can provide a copy constructor in the School that will return a deep copy of the school. In school class provide the copy constructor

/**
 * Copy Constructor to get the copy
 * @return Copy of the school
 */
public static School getSchoolCopy(School school){
 School copy = new School();
 copy.setName(school.getName());
 copy.setAddress(new Address(school.getAddress().getAddress()));
 return copy;
}

And use copy constructor to provide the copy of the object

School s2 = School.getSchoolCopy(s1);

3. Serialize and de-serialize
Use the CommonUtil class (already explained above) to get the copy:

School s1 = CommonUtil.getCopy(s1); to get the clone of the object


The output of all three deep copy methods is:

School Name: General School, Address: School 1 Address
School Name: General School, Address: School 2 Address

Note here, that the change made to the value of "address" in the cloned object (s2) does not change the value of "address" in the original object (s1).

You can also use some third party libraries to create deep copies. Dozer and Kryo are two great libraries that serve this purpose. There is also Apache Commons that provides SerializationUtils.

NOTE:

1) Whenever we copy a Collection of objects we should always go for deep copy.
2) Copy method in the Collections class creates a shallow copy, and hence, modifying the objects in the copied collection will modify the object in the actual collection.

This post is written by Jerin Joseph. He is a freelance writer, loves to explore latest features in Java technology.






No comments:

Post a Comment