Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
EE 497 Java Programming H.I. Bozma Copying Objects in Java 1 Introduction • Concepts and implementation of object copying in Java. • Copying in Java is not something is not used in general in beginning level programming. Required for more advanced level of programming. • Understanding of identity and equality. 2 Copying arrays Consider • Put the brackets after the array name: Point points[ ]; • Copying • You cannot simply create a new pointer and point to the old one as Point[] points2 = points; • 3 Use arraycopy() method of the System class under java.lang package Copying Objects • Object copying -- the clone() method. • All objects inherit clone() from the java.lang.Object class. • A common first mistake is to assume that cloning actually works by default. - The class CloseEx • Compile normally but fail at runtime with a CloneNotSupportedException. • Although the clone method is available to all subclasses of java.lang.Object it is not enabled by default. • To 'turn on' the clone method and prevent the CloneNotSupportedException from being thrown your class must implement the interface java.lang.Cloneable. EE 497 Java Programming H.I. Bozma • This tells the JVM that is OK for it to copy your object. The Cloneable interface is what is known as a marker interface. • Marker interfaces do not contain any methods, you do not need to implement any additional methods. • Simply implementing the interface is enough to change the way the Object::clone() method works. Now the example compiles and runs normally. You will see two lines of output, one for the original object and one for the copy. Note that the values after the '@' are different. The value after the '@' is the objects hash code. By default this is the objects identifier. Think of it as the primary key for the object. On some Java VM's this may be the address in memory of the object. The numbers are different because they are different objects. Cloning creates a new object with details copied from the original. Enable object copying by implementing the Cloneable interface. 4 Constructors Its interesting to note that constructors are not called when a clone is created. Why not? Its not necessary or desirable to call a constructor because you are copying an existing object. It can be assumed that the original object was constructed correctly. Calling a constructor for an object that is going to have its fields copied over is a pointless exercise. The sample program shown below outputs a message to the console each time the constructor is invoked. In this example the constructor is invoked for the original object but not when the copy is made. public class CloneSample implements Cloneable { public CloneSample() { System.out.println("Constructor called"); } public static void main(String[] args) { try { CloneSample original = new CloneSample(); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); } catch(CloneNotSupportedException e) { e.printStackTrace(); EE 497 Java Programming H.I. Bozma } } } Constructors are not called during cloning. 5 Overriding clone() The clone () method that we're inheriting from Object declares that it throws CloneNotSupportedException. CloneNotSupportedException is a checked exception. This means that callers of clone() must wrap it in a try-catch block. If we have implemented the Cloneable interface then we will never get these exceptions. Using the clone() method from Object means that users of our class must write exception handling code that will never be invoked. You can make things simpler for callers of clone() by overriding the inherited version. This allows you to change the method signature and remove the throws CloneNotSupportedException clause. You must still invoke the superclass version of clone. Here is what our sample program looks like: public class CloneSample implements Cloneable { public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { // we implement Cloneable, this exception will never be thrown return null; } } public static void main(String[] args) { CloneSample original = new CloneSample(); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); } } EE 497 Java Programming H.I. Bozma A second reason to override clone() has to do with visibility. Object.clone() is declared as a protected method, it can only be invoked by subclass methods. When overriding a method you can change its visibility to make the method more accessible. Declaring your overridden clone() method public allows any other class to make clones. One thing that you cannot do when overriding clone() is to change the methods return type. It would be convenient to make the clone method return CloneSample as casting the result would not be necessary. It is a requirement of Java that when overriding a method you are not permitted to modify the return type, clone() must always return Object. You must always need to cast the value returned from clone(). Override the clone() method to simplify coding. 6 Issues with Object Copying Object.clone() is a native method written in C. You cannot view the source as it is implemented inside the Java Virtual Machine. How does the method work? Simple, clone() takes a block of memory from the Java heap. The block is the same size as the original object. It then performs a bitwise copy of all fields from the original object into fields in the copy. That is, the fields in the copy are exact duplicates, bit for bit, of the fields in the original. This kind of copy is known as a shallow copy. Shallow Copy - Primitive Types Lets take a simple example, a class that contains only fields of primitive type and look at the result of a clone() operation. Our sample class is defined as: public class CloneSample implements Cloneable { private int number = 1; private boolean flag = true; public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { // we implement Cloneable, this exception will never be thrown return null; } } public static void main(String[] args) { EE 497 Java Programming H.I. Bozma CloneSample original = new CloneSample(); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); } } When clone is invoked the JVM will allocate another object from the Java heap, its size will be the same as the original. The VM then copies all fields, bit by bit, from the original to the copy. In this example these are the values 1 and true. The clone() method returns when the copy process is complete. Any changes made to the copy, for example setting the value of flag to false, will not change the values of fields contained in the original object. Shallow copying of primitive values results in separate values being stored in the copy. Shallow Copy - Reference Types Things change when copying fields containing reference types. Fields that contain objects are actually pointers. You may have been told that Java doesn't support pointers, this is not entirely true. Java uses pointers internally, its just that the programmer can't manipulate the pointers. This is a key difference between Java and languages like C. Shallow copies work bit by bit. In the case of reference types it is the pointer that is copied. The original and the copy will contain pointers to the same object. For example, consider this: public class CloneSample implements Cloneable { private int number = 1; private String name = "abc"; public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { // we implement Cloneable, this exception will never be thrown return null; } } EE 497 Java Programming H.I. Bozma public static void main(String[] args) { CloneSample original = new CloneSample(); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); } } In this example, cloning performs a shallow copy. The field name contains a reference to a String with value "abc", think of it as a pointer to the String. When the bitwise copy is made the name field contains a copy of the pointer. That is, the original and copy both reference the same String. Think of it like this: Sharing a String between copies does not cause any problems because Strings are immutable. Immutable objects are objects that once created cannot be modified. Strings cannot be changed once created. String methods like concat() and replace() create new Strings, they do not modify the original. Wrapper classes like java.lang.Integer are another example of immutable classes. Shallow copying fields that contain references to immutable types are not a problem. Shallow copying fields referencing mutable types is a problem. Fields containing primitive types and immutable reference types can be safely copied using the shallow copy method. 7 Deep Copy Mutable types are objects that can be modified. Collections such as java.util.Vector are mutable types and will cause problems if not handled correctly when copied. Taking a shallow copy of an object containing a collection just copies the pointer, the collection will be shared between the original and clone. For example, consider what happens when cloning this object: public class CloneSample implements Cloneable { private int number = 1; private Vector names = new Vector(); public void update(String data) { // modify the collection names.addElement(data); } EE 497 Java Programming H.I. Bozma public String toString() { return "Sample@" + hashCode() + " contains " + names.size() + " names"; } public static void main(String[] args) { CloneSample original = new CloneSample(); original.update("abc"); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); copy.update("def"); System.out.println("original: " + original); System.out.println("copy: " + copy); } } When you run this example you will see a problem. Calling update("abc") on the original object adds a String to the collection. Calling update("def") against the clone results in a String being added to the original. This happens because a shallow copy was performed, there is only a single Vector object being shared between the original and the clone. Sharing mutable objects between clones is a common source of errors. The solution is to copy the Vector when creating the clone. This is known as a deep copy. Deep copies use an overidden version of clone() to take further copies of mutable objects. Consider the next code sample where a custom clone() method has been added: public class CloneSample implements Cloneable { private int number = 1; EE 497 Java Programming H.I. Bozma private Vector names = new Vector(); public Object clone() { CloneSample copy = null; try { copy = (CloneSample)super.clone(); // make the copy a little deeper copy.names = (Vector)this.names.clone(); } catch(CloneNotSupportedException e) { e.printStackTrace(); } return copy; } public void update(String data) { // modify the collection names.addElement(data); } public String toString() { return "Sample@" + hashCode() + " contains " + names.size() + " names"; } public static void main(String[] args) { CloneSample original = new CloneSample(); original.update("abc"); CloneSample copy = (CloneSample)original.clone(); System.out.println("original: " + original); System.out.println("copy: " + copy); EE 497 Java Programming H.I. Bozma copy.update("def"); System.out.println("original: " + original); System.out.println("copy: " + copy); } } 8 Summary 9 References