Thursday, February 11, 2010

Difficulties of the Java clone method

Sometimes we have a need to copy objects. The clone method is a common way of doing that, but it is all too often misused. This post will discuss some common pitfalls and alternatives to cloning.

First, the Cloneable interface doesn't enforce any contracts. It has no methods. The clone method belongs to Object. Cloneable is just a marker interface to indicate that an object supports cloning. However, in order to make the clone method accessible to other objects, you need to make it public, since it is protected by default (and now all subclasses will have a public clone method, even if they are not directly cloneable since the visibility of the method can't be reduced, so be sure to implement clone for the subclasses).

Additionally, reading the clone method documentation a little more closely, it states the the following are generally true, but not requirements.
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)

Even the stated contract of clone is quite vague, and there really is no guarantee of the object that will be returned. But working on the idea that we want a meaningful clone method, let's assume these will be true and move on to common problems with clone.

First, all objects in the hierarchy must implement clone, and you can't always control that. An implementation of clone will generally look like:

public class CloneMe extends NotCloneable implements Cloneable {
public CloneMe clone() {
try {
// This will fail if NotCloneable doesn't implement the clone method
CloneMe clone = (CloneMe) super.clone();

// copy the other fields
}
catch(CloneNotSupportedException e) {
// do something with the exception
}
}
}

It is difficult to copy objects in the object graph if they too don't
implement clone

public class CloneMe implements Cloneable {
private MyInterface obj;

public CloneMe clone() {
// try/catch omitted for brevity
CloneMe clone = (CloneMe) super.clone();
clone.obj = How can this be copied? We don't even know the type of MyInterface?.
Ideally use obj.clone(), but if it doesn't implement Cloneable, it's
impossible to make a deep copy (if the underlying type has a clone method,
it might be possible to reflectively invoke it).

}
}

Though the above example is illustrated by the fact that obj is an interface, it could have the same problem even if it is a non-final class.

Cloning lists and arrays must also clone the elements of the arrays

public class CloneMe implements Cloneable {
private Object[] myArray;
public CloneMe clone() {
CloneMe clone = (CloneMe) super.clone();
clone.myArray = Arrays.copyOf(myArray); // These arrays refer to the same elements
and modifying one's elements will modify the other's

}
}


Objects that implement clone can't clone final fields. Consider the class

public class CloneMe implements Cloneable {

private final Date myDate;

public CloneMe clone() {
// note: try/catch omitted for brevity
CloneMe clone = (CloneMe) super.clone();

// This won't compile since myDate is final
// This also illustrates a problem discussed above.
// What if date is actually a subclass of date? The clone and
// original objects will have different types for myDate.
clone.myDate = new Date(myDate.getTime());

}
}

Note: If the fields are completely immutable, they don't need to be clone since they can't be changed.

These are some of the major problems with clone, and Joshua Bloch discusses these points as well in Effective Java.

If you need to copy objects, what are the alternatives?

You could use copy constructors. However, copy constructors still have the problem that all of the objects being copied need to be deep copied as well. And since objects being copied may be a subclass of the declared type, the only way to copy them would be to reflectively invoke its copy constructor (if it has one).

You could write your own interface that has a copy method. It would function similarly to the clone method, but it would actually enforce some contract.

The best situation is to avoid object copying whenever possible (create totally new objects if needed). Favor immutability to avoid the need for clone and beware of the pitfalls and document well when copying is absolutely need it.

No comments:

Post a Comment