11.12 OBJECT CLONING IN JAVA


11.12 OBJECT CLONING IN JAVA

We will start with the simplest of examples and build up from there. Consider a simple class like

     class X {         int n;         X() { n = 3; }     } 

We can make an object of this class by

      X xobj = new X(); 

Let's say that we want to clone such objects, in the sense defined at the end of the previous section. As it is, we will not be able to do so, even though the class is simple. For Java to clone an object, the class must implement the interface Cloneable.[9] To make X cloneable, we need to define X as

     class X implements Cloneable {         int n;         X() { n = 3; }      } 

By making X implement the Cloneable interface, X can use the byte-by-byte cloning function, clone(), that it inherits from the root class Object. Suppose we now construct an object of type X by

     X xobj = new X(); 

its clone can then be constructed by

     X xobj_clone = (X) xobj.clone(); 

The reason for casting down to X on the right side is that the Object cloning function deals with only Objects. Recall, on account of polymorphism, every object in Java is an Object (while the converse is of course not true). So the Object cloning function thinks of xobj as being of type Object and makes its byte-by-byte copy that is returned as an Object. The cast operator then casts it down to an object of type X.

The interface Cloneable is empty. It is simply a signal to the Object.clone() function that it is okay to clone objects of type X in our example here. If X did not state explicitly that X implements Cloneable, then the Object.clone() function would throw an exception of type CloneNotSupportedException. What this means is that if you did not want the objects of a certain class to be cloned, you would define that class without the implements Cloneable clause in its header.

Here is a working program for this simple case:

 
//ClonableX.java class X implements Cloneable { public int n; public X() { n = 3; } public static void main(String[] args) { X xobj = new X(); X xobj_clone = null; try { xobj_clone = (X) xobj.clone(); //(A) } catch ( CloneNotSupportedException e ){} System.out.println( xobj.n ); // 3 System.out.println( xobj_clone.n ); // 3 xobj_clone.n = 3000; System.out.println( xobj.n ); // 3 System.out.println( xobj_clone.n ); // 3000 } }

In the above example, we used Object's cloning method directly in line (A). Another approach to doing the same thing would be to write one's own cloning method that invokes Object's cloning method. Doing so serves as a stepping stone to writing cloning methods that are needed when a class possesses non-primitive data members. First, let's show how to write a cloning method that invokes Object's cloning method:

 
//CloneBasic.Java class X implements Cloneable { public int n; public X() { n = 3; } public Object clone() throws CloneNotSupportedException { //(A) return super.clone(); //(B) } } class Test { public static void main( String[] args ) { X xobj = new X(); X xobj_clone = null; try { xobj_clone = (X) xobj.clone(); } catch ( CloneNotSupportedException e ){} System.out.println( xobj.n ); // 3 System.out.println( xobj_clone.n ); // 3 xobj_clone.n = 3000; System.out.println( xobj.n ); // 3 System.out.println( xobj_clone.n ); // 3000 } }

The important thing here is that the class X has its own cloning method in line (A), even though it doesn't do much since all it does is to invokes the cloning method of the superclass in line (B), which is Object's cloning method [10].

Also note that the clone() method we wrote for X has a return type of Object. That return type cannot be changed because clone() of X overrides clone() of Object. As mentioned in Chapter 15, a subclass method that overrides a method defined originally for a superclass is not allowed to change the return type of the overridden method.

While for the simple example above, we could have gotten away with not writing our own cloning method, that's not the case when a class possesses non-primitive data members. There is usually no alternative to writing your own clone() method when your class includes class-type objects for data members. A byte-by-byte copying carried out by Object.clone() may now result in bizarre and unexpected behavior.

In the following example, the class X has an array object as a member. The constructor fills this array with pseudorandom numbers between 0 and 9, both inclusive.[11]

 
//CloneArray.java import java.util.*; // for Random class X implements Cloneable { public int[] arr = new int[5]; public X() { Random ran = new Random(); int i=0; while ( i < 5 ) arr[i++] = (ran.nextInt() & Oxffff)%10; //(A) } public Object clone() throws CloneNotSupportedException { //(B) X xob = null; xob = (X) super.clone(); //now clone the array separately: xob.arr = (int[]) arr.clone(); //(C) return xob; } public String toString() { String printstring = ""; for (int i=0; i<arr.length; i++) printstring += " " + arr[i]; return printstring; } public static void main(String[] args) throws Exception { X xobj = new X(); X xobj_clone = (X) xobj.clone(); //(D) System.out.println( xobj ); // 0 4 5 2 5 System.out.println( xobj_clone ); // 0 4 5 2 5 xobj.arr[0] = 1000; //(E) System.out.println( xobj ); // 1000 4 5 2 5 System.out.println( xobj_clone ); // 0 4 5 2 5 } }

Compared to the previous program CloneBasic.java, the cloning method in line (B) now includes a statement for the cloning of the array object in line (C). Without this statement (that is, if we had opted for the byte-by-byte Object.clone()), the member arr of the cloned object would have acquired the same object reference as held by the member arr in the original object. In other words, arr in the cloned object would have pointed to exactly the same place in the memory as the arr array in the original X object.

By including in line (C) the extra statement for the cloning of the array object, the cloned X object gets a duplicate copy of the original array at a different place in the memory. Subsequently, if we change one of the array element in one of the objects, it would not affect the array in the other object. In the above program, this is demonstrated in main() by first creating in line (D) a clone and then changing the first element of the arr array in the original object in line (E). By printing out the arrays before and after this change, we can see that the change made to the original array did not affect the cloned array.

11.12.1 Cloning Arrays of Class-Type Objects

While the system is happy to clone arrays of primitive types for you (provided, of course, you invoke clone() on the array reference, as we did in line (C) of the previous program), you have to provide your own implementation code if you need to clone arrays (or other container types) of class-type objects. This is demonstrated by the following program in which the class Z has an array data member yarr of type Y[], with each Y element of the array containing a reference to an object of type X. If, for the cloning method of Z, you merely invoke

     yarr.clone(); 

as we have in the commented out line (B) in the program below, the cloned Z object would have its yarr at a different location in the memory, but the X objects contained in the Y elements of the array would be the same as before. This is forestalled by providing our own implementation for the cloning of the array in the method in line (A). This implementation creates a new Y[] array in line (C) and fills this array in line (D) with clones of the elements in the Y[] array of the original Z object. The Y[] array thus created is then assigned to the data member yarr of the cloned Z object in line (E).

 
//CloneClassTypeArr.java class X implements Cloneable { public int p; public X( int q ) { p = q; } public Object clone() throws CloneNotSupportedException { return super.clone(); } public String toString() { return p + ""; } } class Y implements Cloneable { public X x; public Y( X x ) { this.x = x; } public Object clone() throws CloneNotSupportedException { Y clone = (Y) super.clone(); clone.x = (X) x.clone(); return clone; } public String toString() { return x + ""; } } class Z implements Cloneable { public Y[] yarr; public Z( Y[] arr ) { this.yarr = arr; } public Object clone() throws CloneNotSupportedException { //(A) Z zclone = (Z) super.clone(); // zclone.yarr = ( Y[] ) yarr.clone(); // WRONG //(B) Y[] yarrClone = new Y[ yarr.length ]; //(C) for ( int i=0; i < yarr.length; i++ ) yarrClone[i] = (Y) yarr[i].clone(); //(D) zclone.yarr = yarrClone; //(E) return zclone; } public String toString() { String superString = ""; for ( int i = 0; i < yarr.length; i++ ) { superString += yarr[i] + " "; } return superString; } } class Test { public static void main(String[] args) throws Exception { X xobj0 = new X( 5 ); X xobj1 = new X( 7 ); Y yobj0 = new Y( xobj0 ); Y yobj1 = new Y( xobj1 ); Y[] yarr = new Y[2]; yarr[0] = yobj0; yarr[1] = yobj1; Z zobj = new Z( yarr ); System.out.println( zobj ); // 5 7 Z zclone = ( Z ) zobj.clone(); System.out.println( zclone ); // 5 7 zclone.yarr[0].x.p = 1000; System.out.println("\n\nComparing again zobj and its clone:"); System.out.println( zobj ); // 5 7 System.out.println( zclone ); // 1000 7 if ( zobj.yarr == zclone.yarr ) System.out.println( "\n\nThere was no cloning of the Y array" ); if ( zobj.yarr[0].x == zclone.yarr[0].x ) System.out.println("\n\nThe Y array was cloned," + "but its elements point to the same X objects" ); } }

[9]Interfaces in Java, introduced earlier in Chapter 3, are presented more fully in Chapter 15.

[10]If X was extending another class, then the invocation super.clone() in line (B) would take care of the cloning of the data members of the superclass, including those that are private to the superclass.

[11]The call ran.nextInt() in line (A) returns a uniformly distributed pseudorandom integer spanning the full range of the int type. The logical bit-wise AND with 0xffff and then taking division modulo 10 yields non-negative integers between 0 and 9, both inclusive. Another way to generate a pseudorandom number within a specific range is to invoke nextInt with a specific argument. For example, the call

     Random ran = new Random();     ran.nextInt( 10 ); 

yields a pseudorandom integer greater than or equal to 0 but less than 10. Pseudorandom numbers can also be generated in Java by calling the static method Math.random(), which returns a positive double greater than or equal to 0.0 and less than 1.0.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net