Chapter 15: Passing and Copying Objects

15.1: Issues About Passing Handles and Objects

Aliasing happens when a handle is passed to a method as an argument. Both the handle in the caller method and the handle in the called method are pointing to the same object. If the called method only read information from the argument object but do not modify it, this pass-by-reference is efficient. But you cannot prevent the argument from being modified in the method.

In C++ you can use keyword const, or simply use pass-by-value, but in Java these functions are not supported. You have to either code your class so that no public method can modify its data members, or make a copy of the passed argument in the method explicitly and only work on and return that copy.

Handles has scopes, objects do not, because Java objects are all created with dynamic memory allocation.

15.2: Clone an Object

* Do it yourself

In C++ if you want a class to be able to clone itself, you write manually a so-called factory method to return a pointer to the dynamically allocated object. In Java you can do exactly the same.

   
   class Int {
       private int i;
       public Int(int i0)
       {  i = i0; }
       public String toString()
       {  return i; }
       public Int duplicate()
       {  return new Int(i); }
   }
   
   public class Test {
       public static void main(String[] argu)
       {
           Int i1 = new Int(1234);
           Int i2 = i1.duplicate();
           System.out.println("Int i2's value is " + i2.get());
       }
   }

Output will be:

   Int i2's value is 1234

* Using Object's clone method

Java provides another standard approach. In class Object, there is a protected method

   
   protected Object clone()

This well-implemented method is able to use RTTI to find out the size of the derived-class object and make a bitwise copy of it. If you want a class to be clonable, that class should:

1) Implement interface Cloneable.

2) Override method clone( ) and make it public. In the overriding method just simply call super class's clone method.

Cloneable is an empty interface, it works simply as a flag or a label to indicate that you want this class to be cloneable, just like interface Serializable. The reason to introduce this label is that you may want to turn off the cloneability through inheritance, but you can not simply mask a method because Java does not support shrinking inheritance.

If you call the clone( ) method of a class which does not implement interface Cloneable, you will get an CloneNotSupportedException. This is how cloneability is turned off. Because of this exception, when you call super.clone( ) in your overriding clone( ) method, you have to put it in a try block and catch the CloneNotSupportedException.

When the clone( ) method is called, Object.clone( ) will first verify that a class has implemented interface Cloneable. If you have provided a public clone( ) method in your class but haven't implemented Cloneable, it will throw the CloneNotSupportedException.

   
   class Int implements Cloneable {
       private int i;
       public Int(int i0)
       {  i = i0; }
       public int get()
       {  return i; }
   
       public Object clone()
       {
           Object o = null;
           try {
             o = super. clone();
           } catch(CloneNotSupportedException e)
           {  System.out.println("CloneNotSupportedException!"); }
           return o;
       }
   }
   
   public class Test {
       public static void main(String[] argu)
       {
           Int i1 = new Int(1234);
           Int i2 = (Int)i1.clone();
           System.out.println("Int i2's value is " + i2.get());
       }
   }

Output will be the same as last example.

* Comparison of two ways to clone

After seeing these two examples you will surely ask: making my own duplicating method seems to be simpler than using Object's clone( ), so why does Java provide this feature? There are two good reasons:

First, with the C++-like approach, to enable a descendent class to clone itself, all of its ancestor classes upstream should provide some kind of cloning method. In comparison, with Java's clone mechanism, to make a class cloneable, there is no requirements for its ancestor classes at all. It only need to implement clonable and call super.clone in its clone method, and the intelligent Object .clone( ) will do the rest.

Second, because Java does not permit shrinking inheritance, once you provide a duplicating method, all the descendent classes will be duplicable. There is no way to turn off this ability. With the second approach, with the extra requirement to implement interface Cloneable and the use of the CloneNotSupportedException, you can turn off the cloneability at any position in the hierarchy by not implementing Cloneable. You can even prohibit your descendent classes to be cloneable by explicitly throwing CloneNotSupportedException in your overriding clone( ) method.

As you can see in the following example, class Int2 inherits from Int1, and Int1 from Int0. Int0 and Int1 did nothing to support the cloneability of Int2:

   
   class Int0 {
       private int x;
       public Int0(int x0)
       {  x = x0; }
       public String toString()
       {  return "x = " + x; }
   }
   
   class Int1 extends Int0 {
       private int y;
       public Int1(int x0, int y0)
       {
           super(x0);
           y = y0;
       }
       public String toString()
      {  return super. toString() + ", y= " + y; }
   }
   
   class Int2 extends Int1 implements Cloneable {
       private int z;
       public Int2(int x0, int y0, int z0)
       {
           super(x0, y0);
           z = z0;
       }
       public String toString()
      {  return super. toString() + ", z= " + z; }
   
       public Object clone()
       {
           Object o = null;
           try {
            o = super. clone();
           } catch(CloneNotSupportedException e)
           {  System.out.println("CloneNotSupportedException!"); }
           return o;
       }
   }
   
   public class Test {
       public static void main(String[] argu)
       {
           Int2 i1 = new Int2(111, 222, 333);
           Int2 i2 = (Int2)i1.clone();
           System.out.println(i2);
       }
   }

Output will be:

   x = 111, y= 222, z= 333

15.3: Java has no need for Copy Constructor

Both in C++ and Java, copy constructors are not designed for cloning objects explicitly, because copy constructor does not support polymorphism. See next section "Can Copy Constructor Replace Clone( )?" for details.

In C++, objects are by default passed by value, and when it happens, a copy of the argument object is automatically made by the compiler for the called method. Therefore, copy constructor is a must for any class which needs deep copy that default memberwise copy can not achieve. Copy constructor is therefore given big importance and becomes one of the four elements of the OCF: all classes should provide their properly implemented copy constructors unless a default memberwise copy can do the job.

In Java, because all objects are passed by reference and the language even does not support automatic pass-by-value, there is no need for enforcement of copy constructors. For the purpose of cloning objects, Java provides a more robust and automated facility - the clone mechanism discussed above, which, like the factory methods in C++, supports polymorphism, but more intelligently, it does not require the ancestor classes to implement their factory methods like in C++. It sorts out what to do itself using RTTI.

If you want to achieve the effect of pass-by-value - you do not want to work on the passed argument object but a copy of it, you have to use the clone mechanism to clone a new object explicitly.

However, nothing prevents you from making a properly implemented copy constructor for a Java class. With it you can achieve any thing that you can achieve with C++'s constructor.

15.4: Can Copy Constructor Replace Clone( )?

Copy constructor can not be used to clone objects in case of polymorphism, because copy constructor does not support polymorphism. This is true in both C++ and Java.

Suppose you have a abstract class Shape and a series of derived concrete classes such as Circle, Square, Rectangle, etc., and you have a method which receives a concrete-class object with a Shape handle and do something on it. Now if you do not want to modify the original argument but would like to make a copy of it and work on the copy, with clone( ) you can say

   
   public void modifyAndDisplay(Object obj)
   {
      obj = obj. clone();
      ...

If the passed argument is a Circle, the Object handle "obj" will get a Circle, if it's Square, you will get a Square. That's due to the powerful and intelligent implementation (using RTTI) in Object. clone( ). But if you say

   
   public void modifyAndDisplay(Shape obj)
   {
      obj = new Shape(obj);
      ...

because copy constructor can only produce and return an object of its own type, you will only get a Shape object. You will lose all information of the derived-class part of data.

Actually, it is the same in C++. If you want to use polymorphism, you can not directly use copy constructor. You have to put a separate virtual factory method in the base class and implement it properly in all derived classes so that it will return a correct type, like the graphical drawing project of my C++ subject's assignment.

15.5: Shallow Copy and Deep Copy

When an object contains some handles to other composition objects, if a copy constructor or a clone method only duplicates the handles so that the handles in the original object and the duplicated objects are pointing to the same composition objects, this is called a shallow copy. If the composition objects are also copied, together with their own composition objects, and so on, it is called a deep copy.

Object.clone( ) only does shallow copy. This is different from object serialization which sends out a serialized deep copy of the object. This arrangement is reasonable, because deep copy is not always approprite.

Therefore, if you only want to make a shallow copy, what you need to do in your clone( ) method is only to call super.clone( ), just like in the previous examples. However, if you want to make a deep copy, you have to first call super.clone( ), than copy the composition objects yourself. You can use other ways to make the copy, as said before, but calling the clone methods of the composition objects are surely the best way. Those composition objects may or may not be cloneable, so you may decide to catch their possible exceptions too.

   
   class Dog implements Cloneable {
       private String name;
   
       public Dog(String n0)
       {  name = n0; }
   
       public String toString()
       {  return name; }
   
       public void setName(String n0)
       {  name = n0; }
   
       public String getName()
       {  return name; }
   
       public Object clone()
       {
           Object o1 = null;
   
           try {
               o1 = super.clone();
           }
           catch (CloneNotSupportedException e)
           {  System.out.println("CloneNotSupportedException!"); }
   
           return o1;
       }
   }
   
   class Man implements Cloneable {
       private String name;
       private Dog dog;
       public Man(String m, String d)
       {
           name = m;
           dog = new Dog(d);
       }
       public String toString()
       {  return "Man's name: " + name + "\n" + "His dog's name: " + dog; }
   
       public Object clone()
       {
           Man man = null;
   
           try {
               man = (Man)super.clone();
           }
           catch (CloneNotSupportedException e)
           {  System.out.println("CloneNotSupportedException!"); }
           
           return man;
       }
   
       public void setName(String m, String d)
       {
           name = m;
           dog.setName(d);
       }
   }
   
   public class Test {
       public static void main(String[] argu)
       {
           Man m1 = new Man("Frank", "Hoochy");
           Man m2 = (Man)m1.clone();
           System.out.println("m1:\n" + m1 +"\n\nm2:\n" + m2 +
                                    "\n\nNow change m1 to Ellen, Wind...\n");
           m1.setName("Ellen", "Wind");
           System.out.println("m2:\n" + m2 + "\n");
       }
   }

Output will be:

   
   m1:
   Man's name: Frank
   His dog's name: Hoochy
   
   m2:
   Man's name: Frank
   His dog's name: Hoochy
   
   Now change m1 to Ellen, Wind...
   
   m2:
   Man's name: Frank
   His dog's name: Wind

From the output you can see that the handles of the composition objects of the cloned m2 and the original m1 are pointing to the same composition object. Therefore it is a shallow copy.

To make it a deep copy, make class Dog cloneable, and change class Man's clone( ) method to:

   
       public Object clone()
       {
           Man man = null;
   
           try {
               man = (Man)super.clone();
           }
           catch (CloneNotSupportedException e)
           {  System.out.println("CloneNotSupportedException!"); }
   
           man.dog = (Dog)dog.clone();  // possible exception not caught
   
           /* or you can use the alternative:
           man.dog = new Dog(dog.get());  */
   
           return man;
       }

then m2 will not be changed by the changing of m1. So a deep copy is achieved.

15.6: Control Cloneability Through Inheritance Hierarchy

You have several possible attitudes toward the cloneability through the inheritance hierarchy:

* Fully support cloneability

Do as last example. Make your class cloneable and make deep copy if necessary. You may or may not decide to catch the exceptions possibly thrown by the clone( ) methods of the composition objects.

* Reserve cloneability for the descendants of your class

If you do not need deep copy, than you needn't do anything. If you have to implement deep copy, do not implement Cloneable, but override method clone, implement the deep copy, and keep it protected.

* Prevent descendants from being cloneable

Do not implement Cloneable, and throw a CloneNotSupportedException explicitly in your overriding clone( ) method. This would prevent the descendants from being cloneable - unless they decide not to call

super.clone( ) in their own clone( ) method.

Of course you can simply make your class not cloneable and make it final to prevent any one to inherit from it. That's even safer to prevent cloning.

15.7: Immutable Class

There are two ways to achieve the effect of passing by value in Java. First, ask the method who receives the object argument to make a copy of it and work on the copy. Second, let all the set methods of the argument class return a new object with the modified data members, leaving the original untouched.

   
   class Person {
        private String name;
   
        public Person(String s)
        {  name = s; }
   
        public Person setName(String n1)
        {  return new Person(n1); }
   
        public String getName()
        {  return name; }
   
        public String toString()
        {  return name; }
   }
   
   public class Test {
       public static void modify(Person p1)
       {
           p1 = p1.setName("Ellen");
           System.out.println("In method modify(): p1 = p1.setName() = " + p1);
       }
   
       public static void main(String[] argu)
       {
           Person p1 = new Person("Frank");
           modify(p1);
           System.out.println("In main, after passed to modify(), p1 = " + p1);
       }
   }

Output will be:

   
   In method modify(): p1 = p1.set() = Ellen
   In main, after passed to modify(), p1 = Frank

This approach in fact turns all set methods into factory methods. The drawback of this solution is that every time you call a set method a creation of an object happens. In case you have to modify an object very frequently, this will become a heavy performance burden.

15.8: A Pair of Mutable and Immutable Classes

A better solution of the performance burden in last example is to build a pair of classes with the same private data members. The immutable class only provides those get methods to provide information to outside but does not provide any public set methods to change the data. The mutable one provides both get and set public methods. Each class provides a factory method which returns an object of the other class.

Then, when you want to allow a method to modify your object, you pass a mutable copy of the object; when you do not allow, you pass a immutable copy. You can easily create a mutable from an immutable, or immutable from mutable with the factory methods.

The following example showed a more detailed approach using polymorphism:

  1. Make an abstract base class for your class, put all data members in it as protected;
  2. Implement all get methods in the base class;
  3. Leave the implementation empty for all set methods;
  4. Make the factory method abstract in the base class, returning a base-class handle;
  5. Inherit a mutable and an immutable class from the abstract base class, implement constructors, all set methods and the factory method which returns an object of the other class
  6. Manipulate both the mutable and immutable objects with the base-class handle

In this way, the method which receives your class only manipulates the argument with the base-class handle and does not need to know that you have two versions of classes.

   
   abstract class Person {
      protected String name;
   
      public void setName(String n1) {}
   
      public String getName()
      {  return name; }
   
      public String toString()
      {  return name; }
   
       abstract public Person changeMutability();
   }
   
   class MutablePerson extends Person{
      public MutablePerson(String n1)
      {  name = n1; }
   
      public Person changeMutability()
      {  return new ImmutablePerson(name); }
   
      public void setName(String n1)
      {  name = n1; }
   }
   
   class ImmutablePerson extends Person {
      public ImmutablePerson(String n1)
      {  name = n1; }
   
      public Person changeMutability()
      {  return new MutablePerson(name); }
   }
   
   public class Test {
      public static void doNotModify(Person obj)
      {  System.out.println(obj); }
   
      public static void modify(Person obj)
      {
         obj.setName("Ellen Xie");
         System.out.println(obj);
      }
   
      public static void main(String[] argu)
      {
         Person m1 = new ImmutablePerson("Frank Liu");
         doNotModify(m1);
         m1 = m1.changeMutability();  // must use same handle!
         modify(m1);
      }
   }

Output will be:

   
   Frank Liu
   Ellen Xie

Note that when you change the mutability, you must use the same handle, so that the original object lose its handle and get garbage collected. If you use different handles:

   
         Person m2 = m1.changeMutability();  

The original object m1 will stay untouched. With your changing going again and again, there will be more and more useless objects occupying memory - because GC only collect objects without handles.

Make your own free website on Tripod.com