CS 596 Java Programming Fall Semester, 1998 Clone |
||
---|---|---|
© 1998, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 10-Oct-98 |
Clone
Java has a mechanism to allow objects to be copied
A class must:
Using the Default Clone Process
This example shows the minimum effort needed to support the clone method. We will cover exceptions (the throws CloneNotSupportedException) in later lectures.
public class Sample implements Cloneable { private int size = 0; public void setSize( int newSize ) { size = newSize; } public String toString(){ return "Size: " + size; } public Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test { public static void main( String args[] ) throws CloneNotSupportedException { Sample a = new Sample(); Sample b = (Sample) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }Output a Size: 12 b Size: 0
Catching the Exception
The exception "CloneNotSupportedException" only occurrs when the clone method is sent to an object that does not support the cloning. Since the class "Sample" below does support cloning, the exception should not occur in the clone method. We can catch the exception in the clone method as shown below. If the exception does occur then there is a problem with the JVM. That is why we then throw an "InternalError".
Note most of the examples of cloning in this document should have been done this way, but were not for lack of space on the hardcopy slides.
public class Sample implements Cloneable { private int size = 0; public void setSize( int newSize ) { size = newSize; } public String toString(){ return "Size: " + size; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } }
clone() is not "new" This example demonstrates that when you clone an object constructors and initialization block are not called. While not shown here, direct assignments in fields are also not done when cloning objects as they are done when you create an object via "new".
public class CloneIsNotNew implements Cloneable { { System.out.println( "In instance initialization block"); } public CloneIsNotNew( ) { System.out.println( "In constructor"); } public Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test { public static void main( String args[] ) throws Exception { CloneIsNotNew a = new CloneIsNotNew(); System.out.println( "Start clone"); CloneIsNotNew b = (CloneIsNotNew) a.clone(); CloneIsNotNew c = (CloneIsNotNew) a.clone(); } }Output In instance initialization block In constructor Start clone
Problems with Default clone()
The default clone method clones the reference but not object or array that is referenced
Using the default clone on an object with a field that is a reference results in two objects with references to the same item. Let Wrapper be a class that has a field, which is a reference to another object. For this example, the other object as a field of type int. Using the default clone process we get two wrapper objects, a and b, which both reference the same object. Any change in the state of this object will be visible through both objects a and b.
Wrapper a = new Wrapper();
Wrapper b = (Wrapper) a.clone();
Using Default clone with Field References
In this example, the class "BadWrapper" has a field that is a reference to a "Sample" object. The class "BadWrapper" uses the default clone process. In class "TestBadWrapper" a "BadWrapper" object is cloned. Now two "BadWrapper" objects reference the same "Sample" object.
public class Sample implements Cloneable { private int size = 0; public void setSize( int newSize ) { size = newSize; } public String toString(){ return "Size: " + size; } public Object clone() throws CloneNotSupportedException { return super.clone(); } } public class BadWrapper implements Cloneable { private Sample aReference =new Sample(); public void setSize( int newSampleSize ) { aReference.setSize( newSampleSize ); } public String toString() { return "Wrapper " + aReference.toString(); } public Object clone() throws CloneNotSupportedException { return super.clone(); } }
//Using Default clone with Field References - Continued Note that the state of "a" is changed, but when we print out "a" and "b", they have the same state. This is because they reference the same "Sample" object.
public class TestBadWrapper { public static void main( String args[] ) throws Exception { BadWrapper a = new BadWrapper(); BadWrapper b = (BadWrapper) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }
Output a Wrapper Size: 12 b Wrapper Size: 12
Cloning Object References
In this example the field "aReference" is explicitly cloned in the clone method. Now when we clone GoodWrapper objects, each object has its own copies of "Sample" object.
public class GoodWrapper implements Cloneable { private Sample aReference =new Sample(); public void setSize( int newSampleSize ) { aReference.setSize( newSampleSize ); } public String toString() { return "Wrapper " + aReference.toString(); } public Object clone() throws CloneNotSupportedException { GoodWrapper clone = (GoodWrapper) super.clone(); clone.aReference = (Sample) aReference.clone(); return clone; } } public class Test{ public static void main( String args[] ) throws Exception { GoodWrapper a = new GoodWrapper(); GoodWrapper b = (GoodWrapper) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }Output a Wrapper Size: 12 b Wrapper Size: 0
Cloning Arrays
If we have an array of basic types, then the array must be cloned if we do not want the same array referenced in two or more objects. This example shows one way to clone an array.
public class ArrayWrapper implements Cloneable { private int[] aReference = { 0 }; public void setSize( int newSampleSize ) { aReference[0] = newSampleSize; } public String toString() {return "Wrapper " + aReference[0]; } public Object clone() throws CloneNotSupportedException { ArrayWrapper clone = (ArrayWrapper) super.clone(); clone.aReference = (int[]) aReference.clone(); return clone; } } public class Test { public static void main( String args[] ) throws Exception { ArrayWrapper a = new ArrayWrapper(); ArrayWrapper b = (ArrayWrapper) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }Output a Wrapper 12 b Wrapper 0
How not to Clone an Array of Objects Just cloning an array is not good enough if the array contains references to objects or other arrays, as this example shows.
public class BadArrayObjectWrapper implements Cloneable { private Sample[] aReference = {new Sample()}; public void setSize( int newSampleSize ) { aReference[0].setSize( newSampleSize ); } public String toString() { return "Wrapper " + aReference[0].toString(); } public Object clone() throws CloneNotSupportedException { BadArrayObjectWrapper clone = (BadArrayObjectWrapper) super.clone(); clone.aReference = (Sample[]) aReference.clone(); return clone; } } public class Test { public static void main( String args[] ) throws Exception { BadArrayObjectWrapper a = new BadArrayObjectWrapper(); BadArrayObjectWrapper b = (BadArrayObjectWrapper) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }Output a Wrapper Size: 12 b Wrapper Size: 12
How to Clone an Array of Objects You must clone the array and then clone each element of the array.
public class ArrayObjectWrapper implements Cloneable { private Sample[] aReference = {new Sample()}; public void setSize( int newSampleSize ) { aReference[0].setSize( newSampleSize ); } public String toString() { return "Wrapper " + aReference[0].toString(); } public Object clone() throws CloneNotSupportedException { ArrayObjectWrapper clone = (ArrayObjectWrapper) super.clone(); clone.aReference = (Sample[]) aReference.clone(); for (int k = 0; k < aReference.length; k++ ) clone.aReference[k] = (Sample) aReference[k].clone(); return clone; } } public class Test { public static void main( String args[] ) throws Exception { ArrayObjectWrapper a = new ArrayObjectWrapper(); ArrayObjectWrapper b = (ArrayObjectWrapper) a.clone(); a.setSize( 12 ); System.out.println( "a " + a ); System.out.println( "b " + b ); } }Output a Wrapper Size: 12 b Wrapper Size: 0
Non-recursive clone()
There are times when you may not wish to call the clone() method to clone fields of your class. One example is when your field contains circular references (see next slide). Another example is when your field object does not support cloning (see example below). Be warned that copying objects, that do not support clone(), can be very dangerous for two reasons. First, there may be some reason the object did not support clone. Second, you need to copy all the fields in the object.
The example below makes a copy of the field "aReference" by creating a new object, rather than cloning the existing object.
public class Sample { private int size = 0; public void setSize( int newSize ) { size = newSize; } public int getSize( ) { return size;} public String toString(){ return "Size: " + size; } } public class Wrapper implements Cloneable { private Sample aReference = new Sample(); public void setSize( int newSampleSize ) { aReference.setSize( newSampleSize); } public String toString() {return "Wrapper " + aReference; } public Object clone() throws CloneNotSupportedException { Wrapper clone = (Wrapper) super.clone(); clone.aReference = new Sample(); clone.aReference.setSize( aReference.getSize() ); return clone; } }
clone() and Circular References
If an object contains a circular reference (x points to y, y points to x), then one has to be careful about cloning that object. The example below shows the problem. When the object x is cloned, the clone method generates an unending cycle of clone() calls.
public class Node implements Cloneable { public Node next; public Object clone() throws CloneNotSupportedException { System.out.println( "Start clone"); Node clone = (Node) super.clone(); clone.next = (Node) next.clone(); return clone; } }
public class Test { public static void main( String args[] ) throws Exception { Node x = new Node(); Node y = new Node(); x.next = y; y.next = x; // The clone causes infinite recursion Node trouble =(Node) x.clone(); } }
Copyright © 1998 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
All rights reserved.
visitors since 27-Sep-98