An object clone refers to an object which has exactly the same state as the original object. Java by default provides only support for making shallow copies of objects i.e if Java object to be copied contain mutable types like Vector then only their references would be copied and not the actual objects. To overcome this disadvantage Deep copying of objects is recommended. This article explains both the techniques with examples.
Mittwoch, 20. Mai 2009
Java : Object Cloning
Default shallow copying
In Java , an object must implement the Cloneable interface and override the clone() method of an Object class to qualify for cloning.
- The cloneable interface is an empty interface and does not contain any members. It is only used to specify that cloning is supported by this class.
- The clone() method defined in class Object must be overriden in the class supposed to be made cloneable. The clone() method in class Object is declared protected by default.To provide access to any class in any package to this clone() method, the default access modifier should be changed to public. If an object does not implement the Cloneable interface then the clone() method would throw a CloneNotSupportedException. The clone() method returns a reference to a copy of an object.
Example scenario
In the following example a Student's clone is made and subsequently modified. A Student has a Vector of subjects . When this Student clone is made and its Vector of subjects modified then the Vector is updated for the original student also.
ShallowCloning.java
package com.vivek.cloning.example.shallow; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.vivek.cloning.example.valueobjects.Student; /** * Example of Java Shallow copying using Cloneable interface * * @author vivek.tomar@naxos-software.de */ public class ShallowCloning { // Constants-------------------------------------------------------------- private final static Log logger = LogFactory.getLog(ShallowCloning.class.getName()); // Constructors -------------------------------------------------- public ShallowCloning() { // Initialize the classes Vector subjects = new Vector(); subjects.add("Maths"); subjects.add("English"); // Initialize Student Tommy Student tommy = new Student("Tommy", "Jones", subjects); printStudent("Tommy", tommy); // Initialize Student tommyClone Student tommyClone = null; logger.info("Operation : Making clone of Tommy as TommyClone"); long t1 = System.currentTimeMillis(); try { tommyClone = (Student) tommy.clone(); long t2 = System.currentTimeMillis(); logger.info("Time taken: " + (t2 - t1) + " Milliseconds"); // Print both the Students state printStudent("Tommy Clone", tommyClone); logger.info("Operation : Adding more subjects for TommyClone"); // Change the class for Student tommyClone subjects = tommyClone.getSubjects(); subjects.add("History"); subjects.add("Biology"); tommyClone.setSubjects(subjects); printStudent("Tommy Clone", tommyClone); logger.info("Result : The subjects of Tommy were also modified"); printStudent("Tommy", tommy); } catch (CloneNotSupportedException ex) { logger.fatal(ex.getMessage()); } } // Public -------------------------------------------------------- public static void main(String args[]) { new ShallowCloning(); } // Private ------------------------------------------------------- private void printStudent(String instanceName, Student student) { logger.info("Name : " + instanceName); logger.info("State : {" + student.getName() + "," + student.getSurname() + "," + student.getSubjects() + "}"); } }
Student.java
package com.vivek.cloning.example.valueobjects; import java.io.Serializable; import java.util.Vector; /** * Student Value object * * @author vivek.tomar@naxos-software.de */ public class Student implements Cloneable, Serializable { private String name; private String surname; Vector subjects; public Student(String name, String surname, Vector subjects) { this.name = name; this.surname = surname; this.subjects = subjects; } public Vector getSubjects() { return subjects; } public void setSubjects(Vector classes) { this.subjects = classes; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } // /** // * Disable the clone method // */ // @Override // public final Object clone() throws CloneNotSupportedException { // throw new CloneNotSupportedException(); // } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
Result
- Although shallow method copies an instance fast , it fails to copy mutable members like Vectors. For mutable members it only copies the references and not the actual values. This can be big issue since mutable members can be changed without your knowledge in the background.
Deep copying
The solution to overcome the drawback of shallow copying is to do Deep copying. Deep copying not only makes copies of references to other objects along but also makes copies of actual referenced objects. The result is a distinct object with no relation to the original object. Deep copying can be accomplished in following ways.
Deep copying using Cloneable interface and overridden clone() method
This technique is the same as the one utilized for shallow copying except that the overridden clone() method takes care of copying the mutable fields explicitly. It has following issues.
- Custom copying implementations have to be integrated manually for all mutable fields.
- The constructor is not invoked during object copying which can lead to an invalid initialized state and prevent final member fields to be used.
Example scenario
In the following example a Student's clone is made and subsequently modified. A Student has a Vector of subjects . When this Student clone is made and its Vector of subjects modified then the Vector is not updated for the original student .
DeepCloningWithCloneable.java
package com.vivek.cloning.example.deep; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.vivek.cloning.example.valueobjects.StudentWithCustomizedClone; /** * Example of Java Deep copying using Cloneable interface * * @author vivek.tomar@naxos-software.de */ public class DeepCloningWithCloneable { // Constants-------------------------------------------------------------- private final static Log logger = LogFactory.getLog(DeepCloningWithCloneable.class.getName()); // Constructors -------------------------------------------------- public DeepCloningWithCloneable() { // Initialize the classes Vector subjects = new Vector(); subjects.add("Maths"); subjects.add("English"); // Initialize Student Tommy StudentWithCustomizedClone tommy = new StudentWithCustomizedClone("Tommy", "Jones", subjects); printStudent("Tommy", tommy); // Initialize Student tommyClone StudentWithCustomizedClone tommyClone = null; logger.info("Operation : Making deep clone of Tommy as TommyClone"); long t1 = System.currentTimeMillis(); try { tommyClone = (StudentWithCustomizedClone) tommy.clone(); long t2 = System.currentTimeMillis(); logger.info("Time taken: " + (t2 - t1) + " Milliseconds"); // Print both the Students state printStudent("Tommy Clone", tommyClone); logger.info("Operation : Adding more subjects for TommyClone"); // Change the class for Student tommyClone subjects = tommyClone.getSubjects(); subjects.add("History"); subjects.add("Biology"); tommyClone.setSubjects(subjects); printStudent("Tommy Clone", tommyClone); logger.info("Result : The subjects of Tommy were not modified"); printStudent("Tommy", tommy); } catch (CloneNotSupportedException ex) { logger.fatal(ex); } } // Public -------------------------------------------------------- public static void main(String args[]) { new DeepCloningWithCloneable(); } // Private ------------------------------------------------------- private void printStudent(String instanceName, StudentWithCustomizedClone student) { logger.info("Name : " + instanceName); logger.info("State : {" + student.getName() + "," + student.getSurname() + "," + student.getSubjects() + "}"); } }
StudentWithCustomizedClone .java
package com.vivek.cloning.example.valueobjects; import java.io.Serializable; import java.util.Vector; /** * Student Value object * * @author vivek.tomar@naxos-software.de */ public class StudentWithCustomizedClone implements Cloneable, Serializable { private String name; private String surname; Vector subjects; public StudentWithCustomizedClone(String name, String surname, Vector subjects) { this.name = name; this.surname = surname; this.subjects = subjects; } public Vector getSubjects() { return subjects; } public void setSubjects(Vector classes) { this.subjects = classes; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } // /** // * Disable the clone method // */ // @Override // public final Object clone() throws CloneNotSupportedException { // throw new CloneNotSupportedException(); // } @Override public Object clone() throws CloneNotSupportedException { StudentWithCustomizedClone copy = null; try { copy = (StudentWithCustomizedClone) super.clone(); // make the copy a little deeper copy.subjects = (Vector) this.subjects.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } }
Deep copying using Java object serialization
In this technique an object is written out using ObjectOutputStream and then a copy is rebuilt using ObjectInputStream. The following issues are associated with this approach.
- Performance is not good since writing out and reading in operations consume time.
- As above the constructor is not utilized here also.
- The classes to be cloned must implement the Serializable interface.
Apache commons has a utility class called org.apache.commons.lang.SerializationUtils to assist in serialization process.
Scenario
The above mentioned scenario is imitated again using object serialization.
DeepCloning.java
package com.vivek.cloning.example.deep; import java.io.IOException; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.vivek.cloning.example.valueobjects.StudentWithCustomizedClone; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * Example of Java Deep copying using Cloneable interface * * @author vivek.tomar@naxos-software.de */ public class DeepCloning { // Constants-------------------------------------------------------------- private final static Log logger = LogFactory.getLog(DeepCloning.class.getName()); // Constructors -------------------------------------------------- public DeepCloning() { // Initialize the classes Vector subjects = new Vector(); subjects.add("Maths"); subjects.add("English"); // Initialize Student Tommy StudentWithCustomizedClone tommy = new StudentWithCustomizedClone("Tommy", "Jones", subjects); printStudent("Tommy", tommy); // Initialize Student tommyClone StudentWithCustomizedClone tommyClone = null; logger.info("Operation : Making deep clone of Tommy as TommyClone"); long t1 = System.currentTimeMillis(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); try { // Write object ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(tommy); // Read object ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray())); tommyClone = (StudentWithCustomizedClone) in.readObject(); long t2 = System.currentTimeMillis(); logger.info("Time taken: " + (t2 - t1) + " Milliseconds"); // Print both the Students state printStudent("Tommy Clone", tommyClone); logger.info("Operation : Adding more subjects for TommyClone"); // Change the class for Student tommyClone subjects = tommyClone.getSubjects(); subjects.add("History"); subjects.add("Biology"); tommyClone.setSubjects(subjects); printStudent("Tommy Clone", tommyClone); logger.info("Result : The subjects of Tommy were not modified"); printStudent("Tommy", tommy); } catch (IOException ioexception) { logger.fatal(ioexception); } catch (ClassNotFoundException classNotFoundException) { logger.fatal(classNotFoundException.getMessage()); } } // Public -------------------------------------------------------- public static void main(String args[]) { new DeepCloning(); } // Private ------------------------------------------------------- private void printStudent(String instanceName, StudentWithCustomizedClone student) { logger.info("Name : " + instanceName); logger.info("State : {" + student.getName() + "," + student.getSurname() + "," + student.getSubjects() + "}"); } }
Disabling cloning
Cloning can be disabled for a particular object by overriding the clone() method to throw a CloneNotSupportedException.
/**
* Disable the clone method
*/
@Override
public final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Copy Constructors
Copy constructors provide an alternative to clone() method. A constructor is provided which takes in a reference to an object which is to be copied. Copy constructor has its own set of problems.
- The copy constructors are good for final classes only where the class will not be sub classed.
- If used with sub classes , the copy constructor does not help in retaining the sub class type.
- Alternate mechanisms such as reflection must be employed when handling sub class types , which can be really difficult to maintain.
Example demonstrating the copy constructor problem
Scenario
Here I have used a parent Font class and a Verdana subclass. If a copy constructor in Font class is invoked using a Verdana subclass instance then the new copy is of Font class, i.e the subclass type is lost.
CopyConstructor.java
package com.vivek.cloning.copy.construtor; import com.vivek.cloning.example.valueobjects.Font; import com.vivek.cloning.example.valueobjects.Verdana; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * * @author vivek.tomar@naxos-software.de */ public class CopyConstructor { // Constants-------------------------------------------------------------- private final static Log logger = LogFactory.getLog(CopyConstructor.class.getName()); // Public----------------------------------------------------------------- public static void main(String args[]) { Verdana original = new Verdana(); Font copy = new Font(original); logger.info("Original is an object of class " + original.getClass().getSimpleName()); logger.info("Copy is an object of class " + copy.getClass().getSimpleName()); } }
Font.java
package com.vivek.cloning.example.valueobjects; /** * Demonstrating copy constructor problem * * Font super class * * @author vivek.tomar@naxos-software.de */ public class Font { // Attributes ---------------------------------------------------- private String name; // Constructors --------------------------------------------------- public Font(Font font) { this.name = font.name; } public Font() { } // Private ------------------------------------------------------- public String getName() { return name; } public void setName(String name) { this.name = name; } }
Verdana.java
package com.vivek.cloning.example.valueobjects; /** * Font sub class * * @author vivek.tomar@naxos-software.de */ public class Verdana extends Font { // Attributes ---------------------------------------------------- private String color; // Constructor---------------------------------------------------- public Verdana(){ super(); } // Private ------------------------------------------------------- public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
Conclusion
Copy constructors are generally not considered the typical way of copying objects but since they are fast ,for final classes they can definitely be a good alternative. For other classes , deep copying using customized cloneable method for handling mutable types can be a good alternative since it is also fast and handles all types of classes. If performance is not a big issues then Deep copying using serialization can be considered as a stable alternative.

