Sunday, December 7, 2008

No clone for you!

Java Practices call Cloneable/Object#clone API pathalogical and suggest to avoid it. I have to agree. Here's my little story. 


I have a method that receives a certain object, which is Cloneable. Now I want to actually clone that object, so I am calling the clone method. Makes sense, no? Apparently not. My slightly outdated version of IDEA was confused, just as I was, and did not complain. But Javac was cruel and uncompromizing. No clone for you. 


Because Cloneable does not require a clone() method, and Object#clone() can be protected. WTF? My objects subclass different not-necessarily-cloneable classes, so they can't have a common cloneable superclass. So I'll define my own ReallyCloneable interface that has a public clone method, sounds simple enough...? Not!
public interface ReallyCloneable extends Cloneable {
    public Object clone() _
}
Now I realize there's CloneNotSupportedException to throw. A checked exception. I have 2 options: be a good girl, adhere to best practices and clone the signature of Object#clone, or ... not. Let's explore both options. 
public interface ReallyCloneable extends Cloneable {
    public Object clone() throws CloneNotSupportedException;
}
Now all my classes (let's assume they are simple enough to contain only primitives, Strings and arrays of the above) implement ReallyCloneable and add those 3 lines:
public Object clone() throws CloneNotSupportedException {
    return super.clone();
}
But whoever clones my objects also needs to add:
try {
    //call clone
} catch (CloneNotSupportedException e)  {
    //handle it... how???
}
Because implementing Cloneable and all the rest means that JVM will not necessarity throw CloneNotSupportedException, but as far as Javac is concerned, it still may. So no matter what, the caller needs to prepare himself for the worst. Then why bother with interfaces and all that, I ask? People say Java is verbose. I don't mind verbose, but bloated with boiler-plate to this extent?! 

The other option is not to declare the exception in ReallyCloneable. This is possible because overriding rules do not require to throw the exceptions of the overridden method.
public Object clone() {
  try {
    return super.clone();
  } catch (CloneNotSupportedException e) {
    //hm...? 
  }
}
Even though JLS allows it, my IDE and static analysis tools will complain. So I have to either reconfigure them or add some "escape" annotations. More typing... By now I feel like a typomaniac. And wait, I should handle the exception - I don't want to swallow it. Besides, what if someone really objects to being cloned? There is a need for a runtime exception. 
public class CloneFailedException extends RuntimeException {
public CloneFailedException() {
super();
}
  public CloneFailedException(CloneNotSupportedException e) {
    super(e);
  }
}
and
public Object clone() {
  try {
    return super.clone();
  } catch (CloneNotSupportedException e) {
    throw new CloneFailedException(e); 
  }
}
Now at least the callers don't have to catch. But they will still have to cast, because there is no way to express a self type with Java's static types system. Sigh.

And all this why? and for what? Couldn't we just have Cloneable with a public clone and a RuntimeException to throw if we didn't want to be cloned? Simple as that? 

This is yet another example of static type system's failed attempt to protect us from ourselves. 



10 comments:

Paul Beckford said...

Hi Yardena,

A very clear explanation. I wonder why they felt the need to put a CloneNotSupportedException on the interface? And of course making it checked was just silly.

My guess is that it has something to do with state. Without state you can do all this compile time checking stuff, which is why it works well with FP languages like Haskell. When you add state though the compiler has a hard time knowing what to do.

Interestingly OO is all about encapsulated state. So how do the two (static checks and state) go together? I think they don't.

Sun has a new language out called F3. It is part of JavaFX. It seems to be functional at the core, but supports OO too. I haven't been able to find a single technical paper on the language by Sun. All thats out there is marketing blurb.

It would be interesting to know whether they have avoided these pitfalls this time around or just perpetuated the same.

Another thing I've been looking for is a clear explanation of category theory. This is the mathematical underpinnings of all this type theory stuff. Maths doesn't like state either. In maths when you assign something to something else its immutable and doesn't change behind your back :)

So two things I'm on the look out for. A paper on F3 and a simple explanation of category theory. If you've stumbled on either on your travels please let me know.

Cheers,

Paul.

Yardena said...

Hi Paul,

Regarding JavaFX script (former F3) you probably have already seen https://openjfx.dev.java.net and http://javafx.com There are indeed no papers or tech reports, as far as I can see.

Paul Beckford said...

Hi Yardena,

Looking in the JDK it looks as though the clone operation in Object performs a shallow copy. So the object being cloned can only contain immutable objects if it is to be properly copied. If this is true then the object can implement the Clone marker interface and make a call to super.clone() when overriding clone() (which I guess is a call to the method that does the shallow copy in Object).

If your object contains mutable objects then a shallow copy is no good, you need a deep copy. I can't see why you just can't implement a deep copy yourself using recursion, but I think the idea is that you should not implement Clonable and Object will through a CloneNotSupportedException if someone tries to clone you.

Confused? So am I :) But it does appear to be all about state as I guessed.

Yardena said...

Hi Paul,

That's why I said "let's assume only primitives, Strings and array fields". Those get cloned by the JVM. The rest needs to be done by us, otherwise both old and new objects will point at the same object. BTW there will be no exception thrown, just shallow copy. So in real life clone() method is normally not as trivial as I showed. Nonetheless it suffers from the same problems wrt exceptions, casting etc.

Paul Beckford said...

OK. I get it now. So its not the deep copy thing, you can do this yourself if you wish. Its all this nonsense with the marker interface and checked exceptions making everything very verbose.

Well they do say your compiler is your friend. I guess its like in real life where some friends can be bloody annoying :)

Paul.

Paul Beckford said...
This comment has been removed by the author.
Paul Beckford said...

One more question. Why protected on the clone method?

Is there some logic to all of this or were the guys at Sun just smoking something at the time :)

Unknown said...

Just so that we all get a little bit more confused, let me say that even Josh Bloch, who wrote much of the JRE, said that he prefers not to use clone():

http://www.artima.com/intv/blochP.html

He suggests to workaround clone()'s problems by calling constructors directly, which beats polymorphism. No wonder we end up with (horrible) factory methods and dependency injection frameworks.

Paul Beckford said...

I think I just guessed why protected. To force you to implement your own clone. The marker interface Cloneable then seems questionable. Why not use a template method isConeable() which returns false unless you override?

More questions you ask the more confusing it gets. It reminds me of the kind of code you write when your backs against the wall and you've got a deadline to meet.

Java was rushed out in a bit of a panic. I wonder if they are doing the same again with JavaFX Script? Hmmm...

Yardena said...

Thanks for great comments guys.

I remembered reading about clone, but for some reason I thought it was the JLS, while it was in fact Effective Java - thanks for the reference, Itay.

Paul - I agree that marker interface makes no sense. When I originally planned the post, I wanted to make a case against the whole marker interface practice. Serializable is pretty awful too.