John said:
1) The cloning mechanism shall produce from an object a distinct object
of the same class that is logically a copy of the original.
I think that this condition is too strong. If we weaken it to allow an
implementation of copy as:
public <whatever> copy() { return this; }
then the system will work more gracefully.
The 'copy' operation is not a precisely defined operation -- there is no
God-given (or Gosling-given) single idea of what a 'copy' is. So the method
called copy() can only ever represent a class-designers /best guess/ at what is
/likely/ to be most useful to users of that class. In particular, the amount
of state that is replicated (as opposed to shared) between the original and the
copy is not something that can be decided by some global policy. The degree of
'deepness' needed for a copy is determined (even ignoring context) by private
details of the object's implementation, and by the mapping between the objects
state and its semantics. For instance a Rectangle class that internally
maintains its definition/state as two fields of class Point should obviously
(for most normal purposes) implement copy as a fully deep operation. OTOH, a
ColoredRectangle that implemented its state as 4 floats and a Color would
probably implement copy as shallow.
Note that how much state is replicated depends (in part) on details that are
private, and hence the caller (in many circumstances) would be wrong to attempt
to dictate how much of the state is replicated vs. shared.
(Incidentally, that's why I object to, and have avoided using, the method name
clone() -- which I think strongly implies a particular strategy for copying,
and one that is not necessarily appropriate.)
I think that the limiting case of shallowness is a "copy" that replicates no
state, and shares all state. I.e.
return this;
That turns out to be a natural implementation of "copy" for genuine Singletons
(if there are any ;-). And -- much more importantly -- for any object which is
intended to act as the "sole representative" of some other entity. This
interpretation of copy() is that it should return an object that is as distinct
/as semantically possible/ from the original (up to some context and
implementation-dependent limit on deepness). The limiting case of "distinct as
semantically possible" is /no/ difference; if the only semantically valid
"equivalent" of some object is that object itself, then that's what the "copy"
should provide.
Returning to the ColoredRectangle. I said that it would probably implement
copy as a shallow copy (a pure clone()), but really that's a design error.
Rectangle /might/ know how Colors work, and whether the best way to get a
duplicate 'handle' on a colour is simple to copy the reference. It's not
entirely implausible that it /would/ know that, but I don't think it should
/have/ to know it. If Colors are expensive (not likely I admit) then it would
be an entirely plausible design that Color provided a number of pre-defined
instances that (purely for efficiency reasons) should remain unique --
Color.RED, Color.BLUE, -- but that less commonly used instances would be
created on-the-fly and discarded at need. But that's not possible if classes
like Rectangle are going to second-guess what it means to copy a colour. So
the implementation of ColoredRectangle.copy() should also copy() the Color.
The implementation of Color.copy() would just "return this;" (assuming that the
objects were immutable, and if the class designer had thought it worthwhile to
implement an optimised version of copy()).
Anyway, with all that out of the way. How about the following, as a
start-point ?
public class Object
{
....
// the JVM-level clone operation
private native Object __clone();
protected final Object clone()
throws CloneNotSupportedException
{
if (this instanceOf NotClonableMarkerInterface)
throw new CloneNotSupportedException();
return this.__clone();
}
public Object copy()
throws CloneNotSupportedException
{
Object copy = this.clone();
copy.postClone();
return copy;
}
protected void postClone()
{
// default is no action
}
...
}
Some notes:
1) I'm not completely convinced that it's ever appropriate for an object to be
marked with NotClonableMarkerInterface. There may /be/ a good reason, though,
so I've left the test-and-throw in for now. Unless a good use for it can be
found, though, (perhaps something to do with security) then I think it would be
much better to get rid of it and CloneNotSupportedException.
2) I'm not completely convinced that the clone() method should be available to
subclasses at all. It might be better to make clone() private, and change the
copy implementation to something like:
public final Object copy()
throws CloneNotSupportedException
{
if (this instanceOf SingularInstanceMarkerInterface)
return this;
Object copy = this.clone();
copy.postClone();
return copy;
}
but that /reeks/ of over-engineered brittleness to me...
3) Subclasses have the choice of overriding copy(), which they would do if they
wanted to
return this;
or if -- for whatever reason -- they knew a better way to implement copying
than going via the system-level clone() operation.
4) ... or they could override postClone() to do any massaging of the results of
clone() that were necessary to preserve sanity. Nearly all implementations
would take the form:
protected void postClone()
{
m_field1 = m_field1.copy();
m_field6= m_field6.copy();
//...
}
5) In the above I've assumed that the copy() operation should be universally
available. That way seems best to me (especially in a language which makes the
equally problematical equals() method public). The main reasons for
restricting it seem to be that (a) the semantics are /not/ obvious so users
should be forced to think before using it, (b) some objects should not be
copyable. The point (a) is valid, but I don't think that restricting copy()
actually helps. And, as I've said, with the extended interpretation of copy()
that I'm urging, I think that most (perhaps all) of the examples of
non-copyable object evaporate.
6) But, that said, I don't really see much harm in including an /unchecked/
CopyNotSupportedException that subclasses could override copy() to throw.
If we think that copy() should not be supported by default, then I'd just
remove copy() (and postClone()) from Object, add an interface
interface Copyable
{
Object copy();
}
that subclasses could implement at will.
(Incidentally, that pattern and interpretation of "copy" is taken (with
considerable simplification) from Smalltalk. The pattern works well there, but
I wouldn't want to over-state the relevance of that observation since Smalltalk
makes /much/ heavier use of non-cloneable objects than Java does. E.g. the
Integer object 12 cannot be copied, but it does /have/ to be polymorphic with
other objects that /are/ copyable, so implementing copy as "^ self" (i.e.
"return this;" in Java-speak) is the only realistic option).
-- chris