Prevent cloning by making your class final. If clone( )
has not been overridden by any of your ancestor classes, then it cant 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 theyre 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 weve 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 cant 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 doesnt implement Cloneable, so when super.clone( ) is called (which results in a call to Object.clone( )), CloneNotSupportedException is thrown, so the cloning doesnt 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 youll see, in this example its 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.
But what if the programmer doesnt follow the proper path of calling super.clone( ) inside the overridden clone( ) method? In BackOn, you can see how this can happen. This class uses a separate method duplicate( ) to make a copy of the current object and calls this method inside clone( ) instead of calling super.clone( ). The exception is never thrown and the new class is cloneable. You cant rely on throwing an exception to prevent making a cloneable class. The only sure-fire solution is shown in ReallyNoMore, which is final and thus cannot be inherited. That means if clone( ) throws an exception in the final class, it cannot be modified with inheritance, and the prevention of cloning is assured. (You cannot explicitly call Object.clone( ) from a class that has an arbitrary level of inheritance; you are limited to calling super.clone( ), which has access to only the direct base class.) Thus, if you make any objects that involve security issues, youll want to make those classes final.
The first method you see in class CheckCloneable is tryToClone( ), which takes any Ordinary object and checks to see whether its cloneable with instanceof. If so, it casts the object to an IsCloneable, calls clone( ), and casts the result back to Ordinary, catching any exceptions that are thrown. Notice the use of run-time type identification (RTTI; see Chapter 10) to print the class name so you can see whats happening.
In main( ), different types of Ordinary objects are created and upcast to Ordinary in the array definition. The first two lines of code after that create a plain Ordinary object and try to clone it. However, this code will not compile because clone( ) is a protected method in Object. The remainder of the code steps through the array and tries to clone each object, reporting the success or failure of each.
So to summarize, if you want a class to be cloneable:
- Implement the Cloneable interface.
- Override clone( ).
- Call super.clone( ) inside your clone( ).
- Capture exceptions inside your
clone( ).
This will produce the most convenient effects.