The implementation of Object.clone() prevents guaranteed type-safe
(both compile- and runtime) code from compiling. The following
class will not compile:
public class SafeCloner {
public Object cloneOrNull(Cloneable in) {
try {
return in.clone();
} catch(CloneNotSupportedException) {
return null;
}
}
}
(Yes I know that strictly speaking this isn’t type-safe. That’s what this article is about: why it should be.)
It will compile if you cast in to Object and make SafeCloner a member of the
java.lang package. (This is a bug, not a feature — a specification
bug that Java has had since the very beginning.) But it won’t run,
since at runtime no additions to the java.lang package are
accepted. I’m rather relieved, actually. I ended up hacking something together via
reflection, but the java.lang hack was unbelievably dirty.
(Other people, more professional than I, have noted the same problem in different contexts, to do with designing classes for inheritance.)
Details
There are actually two different monumentally flawed design decisions
involved here. The first is the semantic ambiguity in the
protected visibility: protected methods on a class A are
accessible to (a) subclasses of A, and also (b) classes in the
same package as A. So Object.clone() can be called by anything in the java.lang package, but only subclasses outside it. I’ll probably write at greater length elsewhere
exactly what is wrong with this idea, but that’s not the focus of this
article. The other problem is the confusion between runtime and
compile-time checking that is built into the Cloneable/clone()
system of the Java libraries.
Cloneable provides type checking
The Java interface system is intended for compile- and runtime type
checking. We have an interface Cloneable made for this purpose. In
what follows, I’ll distinguish type-safety from “instance-safety”,
which is what exceptions like ArithmeticException are for. In
general type-safety can be guaranteed, while instance-safety
cannot. Java divides type checking into compile time (static) and
runtime (dynamic, strictly speaking “class checking”), with casting
and ClassCastException the simplest example of the latter. (The
generics feature of Java 1.5 moves a lot of the dynamic checking
back to compile time, but is not sigificant for this particular
problem.)
CloneNotSupportedException provides runtime instance checking
The documentation for Object.clone() states (referring to
CloneNotSupportedException):
Subclasses that override the `clone` method can also throw this exception to indicate that an instance cannot be cloned.
This is no longer type checking, but instance checking, and is thus
distinct from the exceptions thrown by mistakenly casting an object to
Cloneable. (Casts are needed because Java is — for acceptable
reasons — not strongly enough typed to guarantee runtime type safety
at compile-time. The CloneNotSupportedException comment above
indicates an intended usage more similar to ArithmeticException, in
case a particular instance of a generally cloneable class cannot be
cloned.)
So far we have a nice clean division into type checking (compile- and runtime) via the interface, and instance checking via the exception. No complaints.
Object.clone() subverts both
And then the bombshell hits. Contrary to all rational expectation,
clone() is not required by Cloneable. It is defined on Object,
and it is protected.
Again from the documentation (this time for Cloneable):
By convention, classes that implement this interface should override `Object.clone` (which is protected) with a public method.
“By convention.” This leaves us with three methods for marking
your class (or instance) as being cloneable: implement Cloneable
(for type checking), avoid throwing CloneNotSupportedException (for
instance checking) and make sure clone() is public.
Now the only time this is useful is at compile time, and we already have perfectly good type checking through the interface mechanism. So what is going on here?
The only answer I’ve managed to come up with is that perhaps the
designers expected that many classes would wish to restrict access to
their clone methods. In that case, you can’t put clone() in the
interface, because this will force it to be public. But they still
wanted to enforce type-safety, so the Object.clone() implementation
requires that it be called on an instance of a Cloneable
class. (This explanation has huge holes, but so does every other I’ve
come up with. It may simply be a case of confusion. For instance, I
don’t see why a separate exception is needed which performs exactly
the same function as a cast to Cloneable in the first line of
Object.clone(). But the biggest question is, if your class has no public clone() method, in what sense is it Cloneable?)
Anyway, this is where the problem hits. Every class implementing
Cloneable (“by convention”) has a public clone() method. But
precisely because this is a convention, there’s no way to inform the
compiler that this is the case. And so the SafeCloner class given
above fails to compile, because clone() is not required by
Cloneable. (Casting in to Object also doesn’t work, because
Object.clone() is protected.)
But why would you want to?
This problem is not purely academic, it turned up in some code I was writing. I’ve pared it down to its minimal expression here for clarity.
The original requirement was a Set that sent events when objects were added or removed. This would be implemented as a wrapper around an original Set, generating the events and handing on the processing to the backing instance.
Now it is sort of built in to the idea here that nothing can be added
to the backing instance without going through the wrapper. Compare for
instance the java.util.Collections static method
unmodifiableCollection(Collection c). This gives you an unmodifiable
view of the input collection c, but any later modifications to c
still show up in the “unmodifiable” collection returned. In the same
way, after creating a WatchableSet w (my admittedly poorly chosen
name) from some Set s, w can unexpectedly change its contents
without sending events, if s gets modified.
So the idea was to take a copy of the incoming Set, and use that
as the backing instance. If we’re dealing with sorted sets, this
becomes a bit problematic: the sort order depends on a Comparator,
and this should most definitely be preserved. Thankfully, almost all
the Collections classes are Cloneable, and I was willing to accept
default behaviour with a big warning in the API for the ones that
weren’t.
So I wanted more-or-less the same as SafeCloner above, except that
the compile-time requirement of Cloneable input would be replaced by
a runtime cast. If either the cast or the clone() failed, the
fallback position is to use the copy constructor required for all
Collection classes and make a new HashSet. (This simply reads the
elements out of the input set and into the new backing instance. So
the class always preserves elements into the backing store, and
usually preserves ordering as well. An alternative fallback is to keep the
input as backing store, guaranteeing the right sort order, but this
subverts the whole intention by making the client keep its sticky
fingers off the backing instance afterwards, which I wanted to avoid, and makes it even more complicated because this requirement only holds in certain unusual cases.)
Then the problem hit. Because the only possible input type for this
constructor is Set. But Set doesn’t guarantee a public clone()
method. Neither does Cloneable. Neither does anything
useful.
What went wrong?
The problem is, quite unexpectedly, that Java does things too
correctly for clone() to fit in. A clone method is fundamentally
problematic for a language that enforces the inheritance of published
interfaces (in the general sense: “I have these methods,” rather than
the specific Java sense of the word).
One of the few places that Java really plays by the book is in the way
inheritance affects visibility. If A extends B means “every A is a
B,” then any method available on a B should also be available on an
A. This is the sort of formal simplication that makes for a clean
type theory, but doesn’t always match real-world coding requirements
(before Java 1.5, the add() method of javax.swing.JFrame was made
effectively private by adding a private enablement check, for
instance).
You might have noticed that constructors in Java aren’t inherited the
way other methods are. This is a classic example of the failure of
this abstraction: a construction method for B does not guarantee
the same construction method will successfully produce an A. And
cloning suffers precisely the same problems.
The constructor problem was solved by building it into the language,
but it’s obviously too late for this to happen for clone(). Instead,
the libary designers have chosen to provide the fewest possible
inheritable guarantees, so that ambivalent cases don’t end up
publishing an interface they (or their subclasses) can’t adhere to.
However this design ignores the existence of runtime checking, which
seems perfectly suitable for this problem. We already have a
CloneNotSupportedException, and on those unfortunate occasions where
B is cloneable but A is not (recall A extends B), A.clone()
throws the exception and that is that.
More generally, there is a conceptual problem here with the notion of
an “interface”. A Java interface should be an interface in the
general sense: a published list of methods that are guaranteed to be
applicable. It is not necessarily a guarantee about the effect of
calling those methods, and this distinction shows up pretty clearly in
this case.
The only sensible published interface for Cloneable is public
Object clone(). Anything else makes nonsense of the very word
“interface”. (Actually the only sensible interface is public
same_class clone() but this is a whole different language quibble.)
Doing it properly
This could actually be retrofitted to the existing libraries without affecting any existing code. Here’s what we need:
package java.lang;
public interface ReallyCloneable extends Cloneable { Object clone(); }
That’s it. And every class that used to implement Cloneable and has a
public clone() method now implements ReallyCloneable
instead. Everything works the way it used to. And new code can use
ReallyCloneable and compile-time check against a public clone(),
while those hypothetical private cloners are still implementing only
Cloneable and providing no public guarantees.
It’s so simple I almost expect to be told that it’s already been done.
Doing it badly: A dirty hack that solves the problem
And finally, the part of the whole story that actually makes me feel unclean.
We’re going to hack together a little reflection-based class that can do our cloning for us. It’s very small, very simple, and very ugly.
import java.lang.reflect.*;
public class Cloner {
public static Object doClone(Object o) throws CloneNotSupportedException {
try {
Method cloneMethod = o.getClass().getMethod("clone",null);
return cloneMethod.invoke(o,null);
} catch(Exception e) {
Throwable ex = new CloneNotSupportedException("Unsuccessful clone()");
ex.initCause(e);
throw ex;
}
}
}
Now I’m going to go and wash.