Prevent cloning by making your class final. If clone( )
has not been overridden by any of your ancestor classes, then it can’t be.
If it has, then override it again and throw CloneNotSupportedException.
Making the class final is the only way to guarantee that cloning is
prevented. In addition, when dealing with security objects or other situations
in which you want to control the number of objects created, you should make all
constructors private and provide one or more special methods for creating
objects. That way, these methods can restrict the number of objects created and
the conditions in which they’re created. (A particular case of this is the
singleton pattern shown in Thinking in Patterns (with Java) at
www.BruceEckel.com.)
//: appendixa:CheckCloneable.java
// Checking to see if a reference can be cloned.
import com.bruceeckel.simpletest.*;
// Can't clone this because it doesn't override clone():
class Ordinary {}
// Overrides clone, but doesn't implement Cloneable:
class WrongClone extends Ordinary {
public Object clone() throws CloneNotSupportedException {
return super.clone(); // Throws exception
}
}
// Does all the right things for cloning:
class IsCloneable extends Ordinary implements Cloneable {
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// Turn off cloning by throwing the exception:
class NoMore extends IsCloneable {
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
class TryMore extends NoMore {
public Object clone() throws CloneNotSupportedException {
// Calls NoMore.clone(), throws exception:
return super.clone();
}
}
class BackOn extends NoMore {
private BackOn duplicate(BackOn b) {
// Somehow make a copy of b and return that copy.
// This is a dummy copy, just to make the point:
return new BackOn();
}
public Object clone() {
// Doesn't call NoMore.clone():
return duplicate(this);
}
}
// You can't inherit from this, so you can't override
// the clone method as you can in BackOn:
final class ReallyNoMore extends NoMore {}
public class CheckCloneable {
private static Test monitor = new Test();
public static Ordinary tryToClone(Ordinary ord) {
String id = ord.getClass().getName();
System.out.println("Attempting " + id);
Ordinary x = null;
if(ord instanceof Cloneable) {
try {
x = (Ordinary)((IsCloneable)ord).clone();
System.out.println("Cloned " + id);
} catch(CloneNotSupportedException e) {
System.err.println("Could not clone " + id);
}
} else {
System.out.println("Doesn't implement Cloneable");
}
return x;
}
public static void main(String[] args) {
// Upcasting:
Ordinary[] ord = {
new IsCloneable(),
new WrongClone(),
new NoMore(),
new TryMore(),
new BackOn(),
new ReallyNoMore(),
};
Ordinary x = new Ordinary();
// This won't compile; clone() is protected in Object:
//! x = (Ordinary)x.clone();
// Checks first to see if a class implements Cloneable:
for(int i = 0; i < ord.length; i++)
tryToClone(ord[i]);
monitor.expect(new String[] {
"Attempting IsCloneable",
"Cloned IsCloneable",
"Attempting WrongClone",
"Doesn't implement Cloneable",
"Attempting NoMore",
"Could not clone NoMore",
"Attempting TryMore",
"Could not clone TryMore",
"Attempting BackOn",
"Cloned BackOn",
"Attempting ReallyNoMore",
"Could not clone ReallyNoMore"
});
}
} ///:~
The first class, Ordinary, represents the kinds of classes we’ve seen throughout this book: no support for cloning, but as it turns out, no prevention of cloning either. But if you have a reference to an Ordinary object that might have been upcast from a more derived class, you can’t tell if it can be cloned or not.
The class WrongClone shows an incorrect way to implement cloning. It does override Object.clone( ) and makes that method public, but it doesn’t implement Cloneable, so when super.clone( ) is called (which results in a call to Object.clone( )), CloneNotSupportedException is thrown, so the cloning doesn’t work.
IsCloneable performs all the right actions for cloning; clone( ) is overridden and Cloneable is implemented. However, this clone( ) method and several others that follow in this example do not catch CloneNotSupportedException, but instead pass it through to the caller, who must then put a try-catch block around it. In your own clone( ) methods you will typically catch CloneNotSupportedException inside clone( ) rather than passing it through. As you’ll see, in this example it’s more informative to pass the exceptions through.
Class NoMore attempts to “turn off” cloning in the way that the Java designers intended: in the derived class clone( ), you throw CloneNotSupportedException. The clone( ) method in class TryMore properly calls super.clone( ), and this resolves to NoMore.clone( ), which throws an exception and prevents cloning.
This will produce the most convenient effects.