Controlling cloneability | CodeGuru

Controlling cloneability

Bruce Eckel’s Thinking in Java Contents | Prev | Next You might suggest that, to remove clonability, the clone( ) method simply be made private, but this won’t work since you cannot take a base-class method and make it more private in a derived class. So it’s not that simple. And yet, it’s necessary to be […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
8 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

You


might suggest that, to remove

clonability,
the
clone( )
method simply be made
private,
but this won’t work since you cannot take a base-class method and make it
more
private
in a derived class. So it’s not that simple. And yet, it’s
necessary to be able to control whether an object can be cloned. There are
actually a number of attitudes you can take to this in a class that you design:
  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.
  2. 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).
  3. Support
    cloning conditionally. If your class holds handles to other objects that might
    or might not be cloneable (an example of this is a collection class), you can
    try to clone all of the objects that you have handles to as part of your
    cloning, and if they throw exceptions just pass them through. For example,
    consider a special sort of
    Vector
    that tries to clone all the objects it holds. When you write such a
    Vector,
    you don’t know what sort of objects the client programmer might put into
    your
    Vector,
    so you don’t know whether they can be cloned.
  4. 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.
  5. 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.
  6. 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 Chapter 16.)

Here’s


an example that shows the various ways cloning can be implemented and then,


later in the hierarchy, “turned off:”

//: CheckCloneable.java
// Checking to see if a handle can be cloned
 
// 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);
  }
}
 
// Can't inherit from this, so can't override
// the clone method like in BackOn:
final class ReallyNoMore extends NoMore {}
 
public class CheckCloneable {
  static Ordinary tryToClone(Ordinary ord) {
    String id = ord.getClass().getName();
    Ordinary x = null;
    if(ord instanceof Cloneable) {
      try {
        System.out.println("Attempting " + id);
        x = (Ordinary)((IsCloneable)ord).clone();
        System.out.println("Cloned " + id);
      } catch(CloneNotSupportedException e) {
        System.out.println(
          "Could not clone " + id);
      }
    }
    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, since clone() is
    // protected in Object:
    //! x = (Ordinary)x.clone();
    // tryToClone() checks first to see if
    // a class implements Cloneable:
    for(int i = 0; i < ord.length; i++)
      tryToClone(ord[i]);
  }
} ///:~ 

The


first class,


Ordinary

,


represents the kinds of classes we’ve seen throughout the book: no


support for cloning, but as it turns out, no prevention of cloning either. But


if you have a handle 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.

In


IsCloneable

you can see all the right actions performed 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 (see Chapter 11) to print out 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. The output is:

Attempting IsCloneable
Cloned IsCloneable
Attempting NoMore
Could not clone NoMore
Attempting TryMore
Could not clone TryMore
Attempting BackOn
Cloned BackOn
Attempting ReallyNoMore
Could not clone ReallyNoMore

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.


The
copy-constructor

Cloning


can seem to be a complicated process to set up. It might seem like there should


be an alternative. One approach that might occur to you (especially if


you’re a C++ programmer) is to make a special constructor whose job it is


to duplicate an object. In C++, this is called the

copy
constructor
.
At first, this seems like the obvious solution. Here’s an example:
//: CopyConstructor.java
// A constructor for copying an object
// of the same type, as an attempt to create
// a local copy.
 
class FruitQualities {
  private int weight;
  private int color;
  private int firmness;
  private int ripeness;
  private int smell;
  // etc.
  FruitQualities() { // Default constructor
    // do something meaningful...
  }
  // Other constructors:
  // ...
  // Copy constructor:
  FruitQualities(FruitQualities f) {
    weight = f.weight;
    color = f.color;
    firmness = f.firmness;
    ripeness = f.ripeness;
    smell = f.smell;
    // etc.
  }
}
 
class Seed {
  // Members...
  Seed() { /* Default constructor */ }
  Seed(Seed s) { /* Copy constructor */ }
}
 
class Fruit {
  private FruitQualities fq;
  private int seeds;
  private Seed[] s;
  Fruit(FruitQualities q, int seedCount) {
    fq = q;
    seeds = seedCount;
    s = new Seed[seeds];
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed();
  }
  // Other constructors:
  // ...
  // Copy constructor:
  Fruit(Fruit f) {
    fq = new FruitQualities(f.fq);
    seeds = f.seeds;
    // Call all Seed copy-constructors:
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed(f.s[i]);
    // Other copy-construction activities...
  }
  // To allow derived constructors (or other 
  // methods) to put in different qualities:
  protected void addQualities(FruitQualities q) {
    fq = q;
  }
  protected FruitQualities getQualities() {
    return fq;
  }
}
 
class Tomato extends Fruit {
  Tomato() {
    super(new FruitQualities(), 100);
  }
  Tomato(Tomato t) { // Copy-constructor
    super(t); // Upcast for base copy-constructor
    // Other copy-construction activities...
  }
}
 
class ZebraQualities extends FruitQualities {
  private int stripedness;
  ZebraQualities() { // Default constructor
    // do something meaningful...
  }
  ZebraQualities(ZebraQualities z) {
    super(z);
    stripedness = z.stripedness;
  }
}
 
class GreenZebra extends Tomato {
  GreenZebra() {
    addQualities(new ZebraQualities());
  }
  GreenZebra(GreenZebra g) {
    super(g); // Calls Tomato(Tomato)
    // Restore the right qualities:
    addQualities(new ZebraQualities());
  }
  void evaluate() {
    ZebraQualities zq =
      (ZebraQualities)getQualities();
    // Do something with the qualities
    // ...
  }
}
 
public class CopyConstructor {
  public static void ripen(Tomato t) {
    // Use the "copy constructor":
    t = new Tomato(t);
    System.out.println("In ripen, t is a " +
      t.getClass().getName());
  }
  public static void slice(Fruit f) {
    f = new Fruit(f); // Hmmm... will this work?
    System.out.println("In slice, f is a " +
      f.getClass().getName());
  }
  public static void main(String[] args) {
    Tomato tomato = new Tomato();
    ripen(tomato); // OK
    slice(tomato); // OOPS!
    GreenZebra g = new GreenZebra();
    ripen(g); // OOPS!
    slice(g); // OOPS!
    g.evaluate();
  }
} ///:~ 

This


seems a bit strange at first. Sure, fruit has qualities, but why not just put


data members representing those qualities directly into the


Fruit

class? There are two potential reasons. The first is that you might want to


easily insert or change the qualities. Note that


Fruit

has a


protected
addQualities( )

method to allow derived classes to do this. (You might think the logical thing


to do is to have a


protected

constructor in


Fruit

that takes a


FruitQualities

argument, but constructors don’t inherit so it wouldn’t be


available in second or greater level classes.) By making the fruit qualities


into a separate class, you have greater flexibility, including the ability to


change the qualities midway through the lifetime of a particular


Fruit

object.

The


second reason for making


FruitQualities

a separate object is in case you want to add new qualities or to change the


behavior via inheritance and polymorphism. Note that for


GreenZebra

(which really is a type of tomato – I’ve grown them and


they’re fabulous), the constructor calls


addQualities( )

and passes it a


ZebraQualities

object, which is derived from


FruitQualities

so it can be attached to the


FruitQualities

handle in the base class. Of course, when


GreenZebra

uses the


FruitQualities

it must downcast it to the correct type (as seen in


evaluate( )

),


but it always knows that type is


ZebraQualities

.

You’ll


also see that there’s a


Seed

class, and that


Fruit

(which by definition carries its own seeds) contains an array of


Seed

s.

Finally,


notice that each class has a copy constructor, and that each copy constructor


must take care to call the copy constructors for the base class and member


objects to produce a deep copy. The copy constructor is tested inside the class


CopyConstructor

.


The method


ripen( )

takes a


Tomato

argument


and performs copy-construction on it in order to duplicate the object:

t
= new Tomato(t);

while


slice( )

takes a more generic


Fruit

object and also duplicates it:

f
= new Fruit(f);

These


are tested with different kinds of


Fruit

in


main( )

.


Here’s the output:

In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit

This


is where the problem shows up. After the copy-construction that happens to the


Tomato

inside


slice( )

,


the result is no longer a


Tomato

object, but just a


Fruit

.


It has lost all of its tomato-ness. Further, when you take a


GreenZebra

,


both


ripen( )

and


slice( )

turn it into a


Tomato

and a


Fruit

,


respectively. Thus, unfortunately, the copy constructor scheme is no good to us


in Java when attempting to make a local copy of an object.


Why
does it work in C++ and not Java?

The


copy constructor is a fundamental part of C++, since it automatically makes a


local copy of an object. Yet the example above proves that it does not work for


Java. Why? In Java everything that we manipulate is a handle, while in C++ you


can have handle-like entities and you can


also

pass around the objects directly. That’s what the C++ copy constructor is


for: when you want to take an object and pass it in by value, thus duplicating


the object. So it works fine in C++, but you should keep in mind that this


scheme fails in Java, so don’t use it.


Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.