How do I perform a deep clone using Serializable?
Author: Deron Eriksson
Description: This Java tutorial shows how to perform a deep copy using the Serializable interface.
Tutorial created using:
Windows Vista || JDK 1.6.0_11 || Eclipse JEE Ganymede SR1 (Eclipse 3.4.1)
The clone() method of Object performs a shallow copy of an object. This means that primitive fields are copied, but objects within the cloned object are not copied. Rather, the references within the new object point to the objects referenced by the original object. This can sometimes lead to unexpected results. Sometimes a deep copy of an object is needed. In a deep copy, rather than references in the new object pointing to the same objects as the original class, the references point to new objects (whose values have been copied over). A typical way of implementing a deep clone is to go through a class and write code to create new objects and copy over all of the values to these new objects. This can be a time-consuming process if the object being cloned is complicated. A simple way of performing a deep clone is for all of the classes that make up a class to implement the Serializable interface. If this is the case, we can serialize all of the values of the object and then deserialize all of these values to a new object. This in essence is a shortcut to performing a deep copy, since all of the values get copied over into new objects. The CloneExample class shows an example of this technique. We can see a standard clone() method that performs a shallow copy. We can also see a deepClone() method that utilizes the Serializable trick to perform the deep copy. The CloneExample class contains two fields, an int and a Thing. These fields are used to demonstrate the differences between a shallow copy (that copies over primitive types but not objects) and a deep copy (that copies over primitive types and objects). CloneExample.javapackage com.cakes; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @SuppressWarnings("serial") public class CloneExample implements Cloneable, Serializable { int num; Thing thing; public CloneExample clone() { try { return (CloneExample) super.clone(); } catch (CloneNotSupportedException e) { return null; } } public CloneExample deepClone() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return (CloneExample) ois.readObject(); } catch (IOException e) { return null; } catch (ClassNotFoundException e) { return null; } } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public Thing getThing() { return thing; } public void setThing(Thing thing) { this.thing = thing; } public String toString() { return "num:" + num + ", thing:" + thing; } } The Thing class is a basic class that contains a String field called 'name'. It is used as a field in CloneExample to demonstrate shallow copying versus deep copying. Thing.javapackage com.cakes; import java.io.Serializable; @SuppressWarnings("serial") public class Thing implements Serializable { String name; public Thing(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String toString() { return name; } } The Demo class demonstrates the shallow and deep cloning. Demo.javapackage com.cakes; public class Demo { public static void main(String[] args) { CloneExample ce = new CloneExample(); ce.setNum(3); ce.setThing(new Thing("Fred")); System.out.println("Before cloning"); System.out.println("ce:" + ce); CloneExample ceShallowClone = ce.clone(); CloneExample cdDeepClone = ce.deepClone(); System.out.println("\nAfter cloning, setting ce num to 5"); ce.setNum(5); System.out.println("After cloning, setting ce thing name to Barney"); Thing thing = ce.getThing(); thing.setName("Barney"); System.out.println("ce:" + ce); System.out.println("ceShallowClone:" + ceShallowClone); System.out.println("cdDeepClone:" + cdDeepClone); System.out.println("\nNotice that changing ce thing name to Barney changed ceShallowClone's thing name to Barney."); System.out.println("This is because the copy was shallow, and ce's thing and ceShallowClone's thing point to the same Thing."); System.out.println("Notice that ceDeepClone's thing name is Fred. This is because the deep copy resulted in ceDeepClone having its own Thing."); } } The console output of executing Demo is shown here. Console OutputBefore cloning ce:num:3, thing:Fred After cloning, setting ce num to 5 After cloning, setting ce thing name to Barney ce:num:5, thing:Barney ceShallowClone:num:3, thing:Barney cdDeepClone:num:3, thing:Fred Notice that changing ce thing name to Barney changed ceShallowClone's thing name to Barney. This is because the copy was shallow, and ce's thing and ceShallowClone's thing point to the same Thing. Notice that ceDeepClone's thing name is Fred. This is because the deep copy resulted in ceDeepClone having its own Thing. Using this technique, the classes that make up the class being cloned need to be Serializable. If they don't, this technique doesn't work. |