Sunday, 17 April 2011

Java Clone, Shallow Copy and Deep Copy

Java provides a mechanism for creating copies of objects called cloning. There are two ways to make a copy of an object called shallow copy and deep copy. See here to understand that.
Java Clone
Java supports shallow and deep copy with the Cloneable interface to create copies of objects. To make a clone of a Java object, you declare that an object implements Cloneable, and then provide an override of the clone method of the standard Java Object base class. Implementing Cloneable tells the java compiler that your object is Cloneable. The cloning is actually done by the clone method.

In java, though clone is ‘intended’ to produce a copy of the same object, though it is not guaranteed. Clone comes with lots of ifs and buts. So my first advice is to not depend on clones. If you want to provide a handle / method to deliver a copy of the current instance write a kind of factory method and provide it with a good documentation. When you are in a situation to use a third party component and produce copies of it using the clone method, then investigate that implementation carefully and get to know what is underlying.

Supporting Shallow Copy by clone

Generally clone method of an object, creates a new instance of the same class and copies all the fields to the new instance and returns it. This is nothing but shallow copy. Object class provides a clone method and provides support for the shallow copy. It returns ‘Object’ as type and you need to explicitly cast back to your original object.

Since the Object class has the clone method (protected) you cannot use it in all your classes. The class which you want to be cloned should implement clone method and overwrite it. It should provide its own meaning for copy or to the least it should invoke the super.clone(). Also you have to implement Cloneable marker interface or else you will get CloneNotSupportedException. When you invoke the super.clone() then you are dependent on the Object class’s implementation and what you get is a shallow copy.

Note that for primitive types, shallow copy works fine, but for classes having other class or user defined types, it may be a problem. So if there is no problem with shallow copies, its fine, otherwise we need deep copies.

Deep Copy

When you need a deep copy then you need to implement it yourself. When the copied object contains some other object its references are copied recursively in deep copy. When you implement deep copy be careful as you might fall for cyclic dependencies. If you don’t want to implement deep copy yourselves then you can go for serialization. It does implements deep copy implicitly and gracefully handling cyclic dependencies.

One more disadvantage with this clone system is that, most of the interface / abstract class writers in java forget to put a public clone method. For example you can take List. So when you want to clone their implementations you have to ignore the abstract type and use actual implementations like ArrayList by name. This completely removes the advantage and goodness of abstractness.

When implementing a singleton pattern, if its superclass implements a public clone() method, to prevent your subclass from using this class’s clone() method to obtain a copy overwrite it and throw a CloneNotSupportedException.

Note that clone is not for instantiation and initialization. It should not be synonymously used as creating a new object. Because the constructor of the cloned objects may never get invoked in the process. It is about copying the object in discussion and not creating new. It completely depends on the clone implementation. One more disadvantage (what to do there are so many), clone prevents the use of final fields. We have to find roundabout ways to copy the final fields into the copied object.

Clone is an agreement between you, compiler and implementer. If you are confident that you all three have good knowledge of java, then go ahead and use clone. If you have a slightest of doubt better copy the object manually.

Example - Shallow and Deep Copy :

import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

public class CloningExample implements Cloneable {

private LinkedList names = new LinkedList();
public CloningExample() {
names.add(“Alex”);
names.add(“Melody”);
names.add(“Jeff”);
}
public String toString() {
StringBuffer sb = new StringBuffer();
Iterator i = names.iterator();
while (i.hasNext()) {
sb.append(“\n\t” + i.next());
}
return sb.toString();
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(“This should not occur since we implement Cloneable”);
}
}
public Object deepClone() {
try {
CloningExample copy = (CloningExample)super.clone();
copy.names = (LinkedList)names.clone();
return copy;
} catch (CloneNotSupportedException e) {
throw new Error(“This should not occur since we implement Cloneable”);
}
}

public boolean equals(Object obj) {

/* is obj reference this object being compared */
if (obj == this) {
return true;
}

/* is obj reference null */
if (obj == null) {
return false;
}

/* Make sure references are of same type */
if (!(this.getClass() == obj.getClass())) {
return false;
} else {
CloningExample tmp = (CloningExample)obj;
if (this.names == tmp.names) {
return true;
} else {
return false;
}
}

}


public static void main(String[] args) {

CloningExample ce1 = new CloningExample();
System.out.println(“\nCloningExample[1]\n” +
“—————–” + ce1);

CloningExample ce2 = (CloningExample)ce1.clone();
System.out.println(“\nCloningExample[2]\n” +
“—————–” + ce2);

System.out.println(“\nCompare Shallow Copy\n” +
“——————–\n” +
“ ce1 == ce2 :+ (ce1 == ce2) + “\n” +
“ ce1.equals(ce2) :+ ce1.equals(ce2));

CloningExample ce3 = (CloningExample)ce1.deepClone();
System.out.println(“\nCompare Deep Copy\n” +
“——————–\n” +
“ ce1 == ce3 :+ (ce1 == ce3) + “\n” +
“ ce1.equals(ce3) :+ ce1.equals(ce3));

System.out.println();

}

}

So as we see in deepClone, lists are explicitly copied.
In this case the shallow copy has the same reference ( == ) and the deep copy only has an equivalent reference ( .equals() ).


No comments:

Post a Comment