Making local copies

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 .

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).

Implementing the Cloneable interface

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.

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.

//: 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

//: 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.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: April 22, 2014 Database professionals -- whether developers or DBAs -- can often save valuable time by learning to get the most from their new or existing productivity tools. Whether you're responsible for managing database projects, performing database health checks and reporting, analyzing code, or measuring software engineering metrics, it's likely you're not taking advantage of some of the lesser-known features of Toad from Dell. Attend this eSeminar with Dell Software's …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds