Making local copies

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Bruce Eckel’s Thinking in Java Contents | Prev | Next

To
review: all argument passing in Java is performed by passing handles. That is,
when you pass “an object,” you’re really passing only a
handle to an object that lives outside the method, so if you perform any
modifications with that handle, you modify the outside object. In addition:

  • Aliasing
    happens automatically during argument passing.
  • There
    are no local objects, only local handles.
  • Handles
    have scopes, objects do not.
  • Object
    lifetime is never an issue in Java.
  • There
    is no language support (e.g. const) to prevent objects from being modified (to
    prevent negative effects of aliasing).
If
you’re only reading information from an object and not modifying it,
passing a handle is the most efficient form of argument passing. This is nice;
the default way of doing things is also the most efficient. However, sometimes
it’s necessary to be able to treat the object as if it were
“local” so that changes you make affect only a local copy and do
not modify the outside object. Many programming languages support the ability
to automatically make a local copy of the outside object, inside the method.
[49]
Java does not, but it allows you to produce this effect.

Pass
by value

  1. Java
    passes everything by value. When you’re passing primitives into a method,
    you get a distinct copy of the primitive. When you’re passing a handle
    into a method, you get a copy of the handle. Ergo, everything is pass by value.
    Of course, the assumption is that you’re always thinking (and caring)
    that handles are being passed, but it seems like the Java design has gone a
    long way toward allowing you to ignore (most of the time) that you’re
    working with a handle. That is, it seems to allow you to think of the handle as
    “the object,” since it implicitly dereferences it whenever you make
    a method call.
  2. Java
    passes primitives by value (no argument there), but objects are passed by
    reference. This is the world view that the handle is an alias for the object,
    so you
    don’t
    think about passing handles, but instead say “I’m passing the
    object.” Since you don’t get a local copy of the object when you
    pass it into a method, objects are clearly not passed by value. There appears
    to be some support for this view within Sun, since one of the “reserved
    but not implemented” keywords is
    byvalue.
    (There’s no knowing, however, whether that keyword will ever see the
    light of day.)

Cloning
objects

The
most likely reason for making a local copy of an object is if you’re
going to modify that object and you don’t want to modify the
caller’s object. If you decide that you want to make a local copy, you
simply use the
clone( )
method to perform the operation. This is a method that’s defined as
protected
in
the base class
Object
and which you must override as
public
in
any derived classes that you want to clone. For example, the standard library
class
Vector
overrides
clone( ),
so we can call
clone( )
for
Vector:

//: Cloning.java
// The clone() operation works for only a few
// items in the standard Java library.
import java.util.*;
 
class Int {
  private int i;
  public Int(int ii) { i = ii; }
  public void increment() { i++; }
  public String toString() {
    return Integer.toString(i);
  }
}
 
public class Cloning {
  public static void main(String[] args) {
    Vector v = new Vector();
    for(int i = 0; i < 10; i++ )
      v.addElement(new Int(i));
    System.out.println("v: " + v);
    Vector v2 = (Vector)v.clone();
    // Increment all v2's elements:
    for(Enumeration e = v2.elements();
        e.hasMoreElements(); )
      ((Int)e.nextElement()).increment();
    // See if it changed v's elements:
    System.out.println("v: " + v);
  }
} ///:~ 

The
clone( )
method produces an
Object,
which must then be recast to the proper type. This example shows how
Vector’s
clone( )

method
does
not

automatically try to clone each of the objects that the
Vector
contains – the old
Vector
and the cloned
Vector
are aliased to the same objects. This is often called a shallow
copy
,
since it’s copying only the “surface” portion of an object.
The actual object consists of this “surface” plus all the objects
that the handles are pointing to, plus all the objects
those
objects
are pointing to, etc. This is often referred to as the “
web
of objects.” Copying the entire mess is called a
deep
copy
.

You
can see the effect of the shallow copy in the output, where the actions
performed on
v2
affect
v:

v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Adding
cloneability to a class

Even
though the clone method is defined in the base-of-all-classes
Object,
cloning is
not
automatically
available in every class.
[51]
This would seem to be counterintuitive to the idea that base-class methods are
always available in derived classes. Cloning in Java goes against this idea; if
you want it to exist for a class, you must specifically add code to make
cloning work.


Using
a trick with protected

    Integer x = new Integer(1);
    x = x.clone();

You
will get, at compile time, an error message that says
clone( )
is
not accessible (since
Integer
doesn’t override it and it defaults to the
protected
version).

You’ll
probably want to override
clone( )
in
any further derived classes, otherwise your (now
public)
clone( )
will be used, and that might not do the right thing (although, since
Object.clone( )
makes a copy of the actual object, it might). The
protected
trick works only once, the first time you inherit from a class that has no
clonability and you want to make a class that’s cloneable. In any classes
inherited from your class the
clone( )
method
is available since it’s not possible in Java to reduce the access of a
method during derivation. That is, once a class is cloneable, everything
derived from it is cloneable unless you use provided mechanisms (described
later) to “turn off” cloning.


Implementing
the Cloneable interface

interface
Cloneable {}

The
reason for implementing this empty
interface
is obviously not because you are going to upcast to
Cloneable
and call one of its methods. The use of
interface
here is considered by some to be a “hack” because it’s using
a feature for something other than its original intent. Implementing the
Cloneable
interface
acts as a kind of a flag, wired into the type of the class.

There
are two reasons for the existence of the
Cloneable
interface.
First, you might have an upcast handle to a base type and not know whether
it’s possible to clone that object. In this case, you can use the
instanceof
keyword (described in Chapter 11) to find out whether the handle is connected
to an object that can be cloned:

if(myHandle
instanceof Cloneable) // ...

Successful
cloning

Once
you understand the details of implementing the
clone( )
method, you’re able to create classes that can be easily duplicated to
provide a local copy:

//: LocalCopy.java
// Creating local copies with clone()
import java.util.*;
 
class MyObject implements Cloneable {
  int i;
  MyObject(int ii) { i = ii; }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("MyObject can't clone");
    }
    return o;
  }
  public String toString() {
    return Integer.toString(i);
  }
}
 
public class LocalCopy {
  static MyObject g(MyObject v) {
    // Passing a handle, modifies outside object:
    v.i++;
    return v;
  }
  static MyObject f(MyObject v) {
    v = (MyObject)v.clone(); // Local copy
    v.i++;
    return v;
  }
  public static void main(String[] args) {
    MyObject a = new MyObject(11);
    MyObject b = g(a);
    // Testing handle equivalence,
    // not object equivalence:
    if(a == b)
      System.out.println("a == b");
    else
      System.out.println("a != b");
    System.out.println("a = " + a);
    System.out.println("b = " + b);
    MyObject c = new MyObject(47);
    MyObject d = f(c);
    if(c == d)
      System.out.println("c == d");
    else
      System.out.println("c != d");
    System.out.println("c = " + c);
    System.out.println("d = " + d);
  }
} ///:~ 

First
of all,
clone( )
must be accessible so you must make it
public.
Second, for the initial part of your
clone( )
operation you should call the base-class version of
clone( ).
The
clone( )
that’s being called here is the one that’s predefined inside
Object,
and you can call it because it’s
protected
and thereby accessible in derived classes.

In
LocalCopy,
the two methods
g( )
and
f( )
demonstrate the difference between the two approaches for argument passing.
g( )
shows passing by reference in which it modifies the outside object and returns
a reference to that outside object, while
f( )
clones the argument, thereby decoupling it and leaving the original object
alone. It can then proceed to do whatever it wants, and even to return a handle
to this new object without any ill effects to the original. Notice the somewhat
curious-looking statement:

v
= (MyObject)v.clone();

This
is where the local copy is created. To prevent confusion by such a statement,
remember that this rather strange coding idiom is perfectly feasible in Java
because everything that has a name is actually a handle. So the handle
v
is used to
clone( )
a copy of what it refers to, and this returns a handle to the base type
Object
(because it’s defined that way in
Object.clone( ))
that must then be cast to the proper type.

In
main( ),
the difference between the effects of the two different argument-passing
approaches in the two different methods is tested. The output is:

a == b
a = 12
b = 12
c != d
c = 47
d = 48

The
effect of Object.clone( )

What
actually happens when
Object.clone( )
is called that makes it so essential to call
super.clone( )
when you override
clone( )
in your class? The
clone( )
method in the root class is responsible for creating the correct amount of
storage and making the bitwise copy of the bits from the original object into
the new object’s storage. That is, it doesn’t just make storage and
copy an
Object
– it actually figures out the size of the precise object that’s
being copied and duplicates that. Since all this is happening from the code in
the
clone( )
method defined in the root class (that has no idea what’s being inherited
from it), you can guess that the process involves
RTTI
to determine the actual object that’s being cloned. This way, the
clone( )
method can create the proper amount of storage and do the correct bitcopy for
that type.

Whatever
you do, the first part of the cloning process should normally be a call to
super.clone( ).
This establishes the groundwork for the cloning operation by making an exact
duplicate. At this point you can perform other operations necessary to complete
the cloning.

To
know for sure what those other operations are, you need to understand exactly
what
Object.clone( )
buys you. In particular, does it automatically clone the destination of all the
handles? The following example tests this:

//: Snake.java
// Tests cloning to see if destination of
// handles are also cloned.
 
public class Snake implements Cloneable {
  private Snake next;
  private char c;
  // Value of i == number of segments
  Snake(int i, char x) {
    c = x;
    if(--i > 0)
      next = new Snake(i, (char)(x + 1));
  }
  void increment() {
    c++;
    if(next != null)
      next.increment();
  }
  public String toString() {
    String s = ":" + c;
    if(next != null)
      s += next.toString();
    return s;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {}
    return o;
  }
  public static void main(String[] args) {
    Snake s = new Snake(5, 'a');
    System.out.println("s = " + s);
    Snake s2 = (Snake)s.clone();
    System.out.println("s2 = " + s2);
    s.increment();
    System.out.println(
      "after s.increment, s2 = " + s2);
  }
} ///:~ 

A
Snake
is made up of a bunch of segments, each of type
Snake.
Thus, it’s a singly-linked list. The segments are created recursively,
decrementing the first constructor argument for each segment until zero is
reached. To give each segment a unique tag, the second argument, a
char,
is incremented for each recursive constructor call.

The
increment( )
method recursively increments each tag so you can see the change, and the
toString( )
recursively prints each tag. The output is:

s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f

Cloning
a composed object

There’s
a problem you’ll encounter when trying to deep copy a composed object.
You must assume that the
clone( )
method in the member objects will in turn perform a deep copy on
their
handles, and so on. This is quite a commitment. It effectively means that for a
deep copy to work you must either control all of the code in all of the
classes, or at least have enough knowledge about all of the classes involved in
the deep copy to know that they are performing their own deep copy correctly.

This
example shows what you must do to accomplish a deep copy when dealing with a
composed object:

//: DeepCopy.java
// Cloning a composed object
 
class DepthReading implements Cloneable {
  private double depth;
  public DepthReading(double depth) {
    this.depth = depth;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}
 
class TemperatureReading implements Cloneable {
  private long time;
  private double temperature;
  public TemperatureReading(double temperature) {
    time = System.currentTimeMillis();
    this.temperature = temperature;
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return o;
  }
}
 
class OceanReading implements Cloneable {
  private DepthReading depth;
  private TemperatureReading temperature;
  public OceanReading(double tdata, double ddata){
    temperature = new TemperatureReading(tdata);
    depth = new DepthReading(ddata);
  }
  public Object clone() {
    OceanReading o = null;
    try {
      o = (OceanReading)super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    // Must clone handles:
    o.depth = (DepthReading)o.depth.clone();
    o.temperature =
      (TemperatureReading)o.temperature.clone();
    return o; // Upcasts back to Object
  }
}
 
public class DeepCopy {
  public static void main(String[] args) {
    OceanReading reading =
      new OceanReading(33.9, 100.5);
    // Now clone it:
    OceanReading r =
      (OceanReading)reading.clone();
  }
} ///:~ 

DepthReading
and
TemperatureReading
are quite similar; they both contain only primitives. Therefore, the
clone( )
method can be quite simple: it calls
super.clone( )
and returns the result. Note that the
clone( )
code for both classes is identical.

OceanReading
is composed of
DepthReading
and
TemperatureReading
objects and so, to produce a deep copy, its
clone( )
must clone the handles inside
OceanReading.
To accomplish this, the result of
super.clone( )
must be cast to an
OceanReading
object (so you can access the
depth
and
temperature
handles).

A
deep copy with Vector

//: AddingClone.java
// You must go through a few gyrations to
// add cloning to your own class.
import java.util.*;
 
class Int2 implements Cloneable {
  private int i;
  public Int2(int ii) { i = ii; }
  public void increment() { i++; }
  public String toString() {
    return Integer.toString(i);
  }
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("Int2 can't clone");
    }
    return o;
  }
}
 
// Once it's cloneable, inheritance
// doesn't remove cloneability:
class Int3 extends Int2 {
  private int j; // Automatically duplicated
  public Int3(int i) { super(i); }
}
 
public class AddingClone {
  public static void main(String[] args) {
    Int2 x = new Int2(10);
    Int2 x2 = (Int2)x.clone();
    x2.increment();
    System.out.println(
      "x = " + x + ", x2 = " + x2);
    // Anything inherited is also cloneable:
    Int3 x3 = new Int3(7);
    x3 = (Int3)x3.clone();
 
    Vector v = new Vector();
    for(int i = 0; i < 10; i++ )
      v.addElement(new Int2(i));
    System.out.println("v: " + v);
    Vector v2 = (Vector)v.clone();
    // Now clone each element:
    for(int i = 0; i < v.size(); i++)
      v2.setElementAt(
        ((Int2)v2.elementAt(i)).clone(), i);
    // Increment all v2's elements:
    for(Enumeration e = v2.elements();
        e.hasMoreElements(); )
      ((Int2)e.nextElement()).increment();
    // See if it changed v's elements:
    System.out.println("v: " + v);
    System.out.println("v2: " + v2);
  }
} ///:~ 

Int3
is inherited from
Int2
and a new primitive member
int
j
is
added. You might think that you’d need to override
clone( )
again to make sure
j
is copied, but that’s not the case. When
Int2’s
clone( )
is called as
Int3’s
clone( ),
it calls
Object.clone( ),
which determines that it’s working with an
Int3
and duplicates all the bits in the
Int3.
As long as you don’t add handles that need to be cloned, the one call to
Object.clone( )
performs all of the necessary duplication, regardless of how far down in the
hierarchy
clone( )
is defined.

You
can see what’s necessary in order to do a deep copy of a
Vector:
after the
Vector
is cloned, you have to step through and clone each one of the objects pointed
to by the
Vector.
You’d have to do something similar to this to do a deep copy of a
Hashtable.

The
remainder of the example shows that the cloning did happen by showing that,
once an object is cloned, you can change it and the original object is left
untouched.

Deep
copy via serialization

//: Compete.java
import java.io.*;
 
class Thing1 implements Serializable {}
class Thing2 implements Serializable {
  Thing1 o1 = new Thing1();
}
 
class Thing3 implements Cloneable {
  public Object clone() {
    Object o = null;
    try {
      o = super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("Thing3 can't clone");
    }
    return o;
  }
}
 
class Thing4 implements Cloneable {
  Thing3 o3 = new Thing3();
  public Object clone() {
    Thing4 o = null;
    try {
      o = (Thing4)super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println("Thing4 can't clone");
    }
    // Clone the field, too:
    o.o3 = (Thing3)o3.clone();
    return o;
  }
}
 
public class Compete {
  static final int SIZE = 5000;
  public static void main(String[] args) {
    Thing2[] a = new Thing2[SIZE];
    for(int i = 0; i < a.length; i++)
      a[i] = new Thing2();
    Thing4[] b = new Thing4[SIZE];
    for(int i = 0; i < b.length; i++)
      b[i] = new Thing4();
    try {
      long t1 = System.currentTimeMillis();
      ByteArrayOutputStream buf =
        new ByteArrayOutputStream();
      ObjectOutputStream o =
        new ObjectOutputStream(buf);
      for(int i = 0; i < a.length; i++)
        o.writeObject(a[i]);
      // Now get copies:
      ObjectInputStream in =
        new ObjectInputStream(
          new ByteArrayInputStream(
            buf.toByteArray()));
      Thing2[] c = new Thing2[SIZE];
      for(int i = 0; i < c.length; i++)
        c[i] = (Thing2)in.readObject();
      long t2 = System.currentTimeMillis();
      System.out.println(
        "Duplication via serialization: " +
        (t2 - t1) + " Milliseconds");
      // Now try cloning:
      t1 = System.currentTimeMillis();
      Thing4[] d = new Thing4[SIZE];
      for(int i = 0; i < d.length; i++)
        d[i] = (Thing4)b[i].clone();
      t2 = System.currentTimeMillis();
      System.out.println(
        "Duplication via cloning: " +
        (t2 - t1) + " Milliseconds");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~ 

Thing2
and
Thing4
contain member objects so that there’s some deep copying going on.
It’s interesting to notice that while
Serializable
classes are easy to set up, there’s much more work going on to duplicate
them. Cloning involves a lot of work to set up the class, but the actual
duplication of objects is relatively simple. The results really tell the tale.
Here is the output from three different runs:

Duplication via serialization: 3400 Milliseconds
Duplication via cloning: 110 Milliseconds
 
Duplication via serialization: 3410 Milliseconds
Duplication via cloning: 110 Milliseconds
 
Duplication via serialization: 3520 Milliseconds
Duplication via cloning: 110 Milliseconds

Despite
the obviously huge time difference between serialization and cloning,
you’ll also notice that the serialization technique seems to vary
significantly in its duration, while cloning takes the same amount of time
every time.

Adding
cloneability

further
down a hierarchy

//: HorrorFlick.java
// You can insert Cloneability at any
// level of inheritance.
import java.util.*;
 
class Person {}
class Hero extends Person {}
class Scientist extends Person
    implements Cloneable {
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      // this should never happen:
      // It's Cloneable already!
      throw new InternalError();
    }
  }
}
class MadScientist extends Scientist {}
 
public class HorrorFlick {
  public static void main(String[] args) {
    Person p = new Person();
    Hero h = new Hero();
    Scientist s = new Scientist();
    MadScientist m = new MadScientist();
 
    // p = (Person)p.clone(); // Compile error
    // h = (Hero)h.clone(); // Compile error
    s = (Scientist)s.clone();
    m = (MadScientist)m.clone();
  }
} ///:~ 

Before
clonability was added, the compiler stopped you from trying to clone things.
When clonability is added in
Scientist,
then
Scientist
and all its descendants are cloneable.

Why
this strange design?

If
all this seems to be a strange scheme, that’s because it is. You might
wonder why it worked out this way. What is the meaning behind this design? What
follows is not a substantiated story – probably because much of the
marketing around Java makes it out to be a perfectly-designed language –
but it does go a long way toward explaining how things ended up the way they did.

Originally,
Java was designed as a language to control hardware boxes, and definitely not
with the Internet in mind. In a general-purpose language like this, it makes
sense that the programmer be able to clone any object. Thus,
clone( )
was placed in the root class
Object,
but
it was a
public
method so you could always clone any object. This seemed to be the most
flexible approach, and after all, what could it hurt?

Well,
when Java was seen as the ultimate Internet programming language, things
changed. Suddenly, there are security issues, and of course, these issues are
dealt with using objects, and you don’t necessarily want anyone to be
able to clone your security objects. So what you’re seeing is a lot of
patches applied on the original simple and straightforward scheme:
clone( )
is now
protected
in
Object.
You must override it
and
implement
Cloneable

and
deal with the exceptions.


[49]
In C, which generally handles small bits of data, the default is pass-by-value.
C++ had to follow this form, but with objects pass-by-value isn’t usually
the most efficient way. In addition, coding classes to support pass-by-value in
C++ is a big headache.

[50]
This is not the dictionary spelling of the word, but it’s what is used in
the Java library, so I’ve used it here, too, in some hopes of reducing
confusion.

[51]
You can apparently create a simple counter-example to this statement, like this:

public
class Cloneit implements Cloneable {


public static void main (String[] args)


throws CloneNotSupportedException {


Cloneit a = new Cloneit();


Cloneit b = (Cloneit)a.clone();


}

}

However,
this only works because
main( )
is a method of
Cloneit
and thus has permission to call the
protected
base-class method
clone( ).
If you call it from a different class, it won’t compile.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read