Download as odt, pdf, or txt
Download as odt, pdf, or txt
You are on page 1of 6

The concept of deep copy

In order to understand what a deep copy is, let's first look at the concept of shallow copying. 
In a previous JavaWorld article, "How to avoid traps and correctly override methods from 
java.lang.Object," Mark Roulo explains how to clone objects as well as how to achieve shallow 
copying instead of deep copying. To summarize briefly here, a shallow copy occurs when an object 
is copied without its contained objects. To illustrate, Figure 1 shows an object, obj1, that contains 
two objects, containedObj1 and containedObj2. 

Figure 1. The original state of obj1
If a shallow copy is performed on obj1, then it is copied but its contained objects are not, as shown 
in Figure 2. 

Figure 2. After a shallow copy of obj1
A deep copy occurs when an object is copied along with the objects to which it refers. Figure 3 
shows obj1 after a deep copy has been performed on it. Not only has obj1 been copied, but the 
objects contained within it have been copied as well. 

Figure 3. After a deep copy of obj1
If either of these contained objects themselves contain objects, then, in a deep copy, those objects 
are copied as well, and so on until the entire graph is traversed and copied. Each object is 
responsible for cloning itself via its clone() method. The default clone() method, inherited 
from Object, makes a shallow copy of the object. To achieve a deep copy, extra logic must be 
added that explicitly calls all contained objects' clone() methods, which in turn call their 
contained objects' clone() methods, and so on. Getting this correct can be difficult and time 
consuming, and is rarely fun. To make things even more complicated, if an object can't be modified 
directly and its clone() method produces a shallow copy, then the class must be extended, the 
clone() method overridden, and this new class used in place of the old. (For example, Vector
does not contain the logic necessary for a deep copy.) And if you want to write code that defers until 
runtime the question of whether to make a deep or shallow copy an object, you're in for an even 
more complicated situation. In this case, there must be two copy functions for each object: one for a 
deep copy and one for a shallow. Finally, even if the object being deep copied contains multiple 
references to another object, the latter object should still only be copied once. This prevents the 
proliferation of objects, and heads off the special situation in which a circular reference produces an 
infinite loop of copies. 

Serialization
Back in January of 1998, JavaWorld initiated its JavaBeans column by Mark Johnson with an 
article on serialization, "Do it the 'Nescafé' way ­­ with freeze­dried JavaBeans." To summarize, 
serialization is the ability to turn a graph of objects (including the degenerate case of a single 
object) into an array of bytes that can be turned back into an equivalent graph of objects. An object 
is said to be serializable if it or one of its ancestors implements java.io.Serializable or 
java.io.Externalizable. A serializable object can be serialized by passing it to the 
writeObject() method of an ObjectOutputStream object. This writes out the object's 
primitive data types, arrays, strings, and other object references. The writeObject() method is 
then called on the referred objects to serialize them as well. Further, each of these objects have their 
references and objects serialized; this process goes on and on until the entire graph is traversed and 
serialized. Does this sound familiar? This functionality can be used to achieve a deep copy. 

Deep copy using serialization
The steps for making a deep copy using serialization are:
1. Ensure that all classes in the object's graph are serializable.
2. Create input and output streams.
3. Use the input and output streams to create object input and object output streams.
4. Pass the object that you want to copy to the object output stream.
5. Read the new object from the object input stream and cast it back to the class of the object 
you sent. 

I have written a class called ObjectCloner that implements steps two through five. The line 
marked "A" sets up a ByteArrayOutputStream which is used to create the 
ObjectOutputStream on line B. Line C is where the magic is done. The writeObject()
method recursively traverses the object's graph, generates a new object in byte form, and sends it to 
the ByteArrayOutputStream. Line D ensures the whole object has been sent. The code on 
line E then creates a ByteArrayInputStream and populates it with the contents of the 
ByteArrayOutputStream. Line F instantiates an ObjectInputStream using the 
ByteArrayInputStream created on line E and the object is deserialized and returned to the 
calling method on line G. Here's the code: 

import java.io.*;
import java.util.*;
import java.awt.*;
public class ObjectCloner
{
// so that nobody can accidentally create an ObjectCloner object
private ObjectCloner(){}
// returns a deep copy of an object
static public Object deepCopy(Object oldObj) throws Exception
{
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try
{
ByteArrayOutputStream bos =
new ByteArrayOutputStream(); // A
oos = new ObjectOutputStream(bos); // B
// serialize and pass the object
oos.writeObject(oldObj); // C
oos.flush(); // D
ByteArrayInputStream bin =
new ByteArrayInputStream(bos.toByteArray()); // E
ois = new ObjectInputStream(bin); // F
// return the new object
return ois.readObject(); // G
}
catch(Exception e)
{
System.out.println("Exception in ObjectCloner = " + e);
throw(e);
}
finally
{
oos.close();
ois.close();
}
}

All a developer with access to ObjectCloner is left to do before running this code is ensure that 
all classes in the object's graph are serializable. In most cases, this should have been done already; if 
not, it ought to be relatively easy to do with access to the source code. Most of the classes in the 
JDK are serializable; only the ones that are platform­dependent, such as FileDescriptor, are 
not. Also, any classes you get from a third­party vendor that are JavaBean­compliant are by 
definition serializable. Of course, if you extend a class that is serializable, then the new class is also 
serializable. With all of these serializable classes floating around, chances are that the only ones you 
may need to serialize are your own, and this is a piece of cake compared to going through each class 
and overwriting clone() to do a deep copy. 
An easy way to find out if you have any nonserializable classes in an object's graph is to assume that 
they are all serializable and run ObjectCloner's deepCopy() method on it. If there is an 
object whose class is not serializable, then a java.io.NotSerializableException will be 
thrown, telling you which class caused the problem. 
A quick implementation example is shown below. It creates a simple object, v1, which is a Vector
that contains a Point. This object is then printed out to show its contents. The original object, v1, 
is then copied to a new object, vNew, which is printed to show that it contains the same value as 
v1. Next, the contents of v1 are changed, and finally both v1 and vNew are printed so that their 
values can be compared. 

import java.util.*;
import java.awt.*;
public class Driver1
{
static public void main(String[] args)
{
try
{
// get the method from the command line
String meth;
if((args.length == 1) &&
((args[0].equals("deep")) || (args[0].equals("shallow"))))
{
meth = args[0];
}
else
{
System.out.println("Usage: java Driver1 [deep, shallow]");
return;
}
// create original object
Vector v1 = new Vector();
Point p1 = new Point(1,1);
v1.addElement(p1);
// see what it is
System.out.println("Original = " + v1);
Vector vNew = null;
if(meth.equals("deep"))
{
// deep copy
vNew = (Vector)(ObjectCloner.deepCopy(v1)); // A
}
else if(meth.equals("shallow"))
{
// shallow copy
vNew = (Vector)v1.clone(); // B
}
// verify it is the same
System.out.println("New = " + vNew);
// change the original object's contents
p1.x = 2;
p1.y = 2;
// see what is in each one now
System.out.println("Original = " + v1);
System.out.println("New = " + vNew);
}
catch(Exception e)
{
System.out.println("Exception in main = " + e);
}
}
}
To invoke the deep copy (line A), execute java.exe Driver1 deep. When the deep copy 
runs, we get the following printout: 

Original = [java.awt.Point[x=1,y=1]]
New = [java.awt.Point[x=1,y=1]]
Original = [java.awt.Point[x=2,y=2]]
New = [java.awt.Point[x=1,y=1]]

This shows that when the original Point, p1, was changed, the new Point created as a result of 
the deep copy remained unaffected, since the entire graph was copied. For comparison, invoke the 
shallow copy (line B) by executing java.exe Driver1 shallow. When the shallow copy 
runs, we get the following printout: 

Original = [java.awt.Point[x=1,y=1]]
New = [java.awt.Point[x=1,y=1]]
Original = [java.awt.Point[x=2,y=2]]
New = [java.awt.Point[x=2,y=2]]

This shows that when the original Point was changed, the new Point was changed as well. This 
is due to the fact that the shallow copy makes copies only of the references, and not of the objects to 
which they refer. This is a very simple example, but I think it illustrates the, um, point. 

Implementation issues
Now that I've preached about all of the virtues of deep copy using serialization, let's look at some 
things to watch out for.
The first problematic case is a class that is not serializable and that cannot be edited. This could 
happen, for example, if you're using a third­party class that doesn't come with the source code. In 
this case you can extend it, make the extended class implement Serializable, add any (or all) 
necessary constructors that just call the associated superconstructor, and use this new class 
everywhere you did the old one (here is an example of this). 
This may seem like a lot of work, but, unless the original class's clone() method implements 
deep copy, you will be doing something similar in order to override its clone() method anyway. 
The next issue is the runtime speed of this technique. As you can imagine, creating a socket, 
serializing an object, passing it through the socket, and then deserializing it is slow compared to 
calling methods in existing objects. Here is some source code that measures the time it takes to do 
both deep copy methods (via serialization and clone()) on some simple classes, and produces 
benchmarks for different numbers of iterations. The results, shown in milliseconds, are in the table 
below: 
Procedure\Iterations(n) 1000 10000 100000
clone 10 101 791
serialization 1832 11346 107725
Milliseconds to deep copy a simple class graph n times
As you can see, there is a large difference in performance. If the code you are writing is 
performance­critical, then you may have to bite the bullet and hand­code a deep copy. If you have a 
complex graph and are given one day to implement a deep copy, and the code will be run as a batch 
job at one in the morning on Sundays, then this technique gives you another option to consider. 
Another issue is dealing with the case of a class whose objects' instances within a virtual machine 
must be controlled. This is a special case of the Singleton pattern, in which a class has only one 
object within a VM. As discussed above, when you serialize an object, you create a totally new 
object that will not be unique. To get around this default behavior you can use the 
readResolve() method to force the stream to return an appropriate object rather than the one 
that was serialized. In this particular case, the appropriate object is the same one that was 
serialized. Here is an example of how to implement the readResolve() method. You can find 
out more about readResolve() as well as other serialization details at Sun's Web site dedicated 
to the Java Object Serialization Specification (see Resources). 
One last gotcha to watch out for is the case of transient variables. If a variable is marked as 
transient, then it will not be serialized, and therefore it and its graph will not be copied. Instead, the 
value of the transient variable in the new object will be the Java language defaults (null, false, and 
zero). There will be no compiletime or runtime errors, which can result in behavior that is hard to 
debug. Just being aware of this can save a lot of time. 
The deep copy technique can save a programmer many hours of work but can cause the problems 
described above. As always, be sure to weigh the advantages and disadvantages before deciding 
which method to use. 

Conclusion
Implementing deep copy of a complex object graph can be a difficult task. The technique shown 
above is a simple alternative to the conventional procedure of overwriting the clone() method for 
every object in the graph. 

About the author
Dave Miller is a senior architect with the consulting firm Javelin Technology, where he works on 
Java and Internet applications. He has worked for companies such as Hughes, IBM, Nortel, and 
MCIWorldcom on object­oriented projects, and has worked exclusively with Java for the past three 
years. 

You might also like