Sunday 30 November 2008

Item 11 - Override Clone Judiciously

When I started reading this item I couldn't quite believe it. So I checked the Java documentation and sure enough Object does have a protected clone method and the Cloneable interface does not have any methods. Then I started wondering how adding an interface with no methods could stop a super class protected method from throwing. So I tried it:
public class AnObject
{
public AnObject clone()
{
AnObject obj = null;

try
{
obj = (AnObject)super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}

return obj;
}


public static void main(String[] args)
{
new AnObject().clone();
}
}
Sure enough, it threw CloneNotSupportedException. So I added the Cloneable interface:
public class AnObject implements Cloneable
{
...

public static void main(String[] args)
{
new AnObject().clone();
}
}
and the exception went away. I was puzzled until I remembered Java's reflection mechanism. Object's protected clone method, when called, must check to make sure that subclasses implement the Cloneable interface and throw the exception if they don't. Mystery over. However, it's a shame that the item didn't explain that explicitly.

The item does point out that although Object's clone method returns an Object, since Java 1.5 covariant return types allow an overriding method to return a different type to the method it is overriding. Based on this I wonder if the Cloneable interface ought to look more like this:
public interface Cloneable 
{
T clone();
}
The the subclass would look like this:
public class AnObject implements Cloneable
{
@Override
public AnObject clone()
{
AnObject obj = null;

try
{
obj = (AnObject)super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}

return obj;
}
}
This of course wont work with the current implementation as Object's clone method still looks for the original Cloneable interface.

The things to remember are:
  • If you override the clone method in a nonfinal class, you should return the object obtained by invoking super.clone.
  • In practice, a class that implements Cloneable is expected to provide a properly functioning clone method

The item then goes on to discuss the need for implementing deep copying when writing a clone method for a class that refers to other objects. Cloning the Stack example shown in the item, without performing a deep copy, results in two Stack objects both referring to the same elements. This can be overcome by making the clone method clone the elements as well as the Stack object.

Finally the item explains that it is better to provide an alternative means of object copying, or simply not providing the capability. The alternatives it recommends are a copy constructor:

public AnObject(AnObject obj);

or a copy factory:

public static AnObject newInstance(AnObject obj);

I think the item presents a reasonable case for generally avoiding the Cloneable interface.

Paul Grenyer

No comments: