Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in Java
Prev Contents / Index Next

Controlling cloneability

You might suggest that, to remove cloneability, the clone( ) method should simply be made private, but this won’t work, because you cannot take a base-class method and make it less accessible in a derived class. And yet, it’s necessary to be able to control whether an object can be cloned. There are a number of attitudes you can take to this for your classes:

  1. Indifference. You don’t do anything about cloning, which means that your class can’t be cloned, but a class that inherits from you can add cloning if it wants. This works only if the default Object.clone( ) will do something reasonable with all the fields in your class. Support clone( ). Follow the standard practice of implementing Cloneable and overriding clone( ). In the overridden clone( ), you call super.clone( ) and catch all exceptions (so your overridden clone( ) doesn’t throw any exceptions).
  2. Support cloning conditionally. If your class holds references to other objects that might or might not be cloneable (a container class, for example), your clone( ) can try to clone all of the objects for which you have references, and if they throw exceptions, just pass those exceptions out to the programmer. For example, consider a special sort of ArrayList that tries to clone all the objects it holds. When you write such an ArrayList, you don’t know what sort of objects the client programmer might put into your ArrayList, so you don’t know whether they can be cloned. Don’t implement Cloneable but override clone( ) as protected, producing the correct copying behavior for any fields. This way, anyone inheriting from this class can override clone( ) and call super.clone( ) to produce the correct copying behavior. Note that your implementation can and should invoke super.clone( ) even though that method expects a Cloneable object (it will throw an exception otherwise), because no one will directly invoke it on an object of your type. It will get invoked only through a derived class, which, if it is to work successfully, implements Cloneable.
  3. Try to prevent cloning by not implementing Cloneable and overriding clone( ) to throw an exception. This is successful only if any class derived from this calls super.clone( ) in its redefinition of clone( ). Otherwise, a programmer may be able to get around it.
  4. 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.

    But what if the programmer doesn’t 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 can’t 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, you’ll 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 it’s 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 what’s 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:

    1. Implement the Cloneable interface.
    2. Override clone( ).
    3. Call super.clone( ) inside your clone( ).
    4. Capture exceptions inside your clone( ).

    This will produce the most convenient effects.
    Thinking in Java
    Prev Contents / Index Next


 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire