程式扎記: [ Data Structures with Java ] Section 9.5 : Cloneable Objects

標籤

2011年2月6日 星期日

[ Data Structures with Java ] Section 9.5 : Cloneable Objects

Preface : 
In designing a class, it is sometimes useful to have an operation that makes a copy, or clone, of the object. Ideally, the result should be two separated objects so that an updated to one will not affect the other. Some languages such as C++ provide this capability with a copy constructor. The idea is illustrated in the String class, which has a constructor that takes a string argument and create a new string with the same sequence of characters. 
In Java, the standard approach has a class using a cloning mechanism that relies on the Object clone() method. The method performs a field-for-field copy of the object. The following is a declaration of clone() in the Object class. The keyword native means that the implementation of the method is in another programming language, usually C. 

- Object clone() :
  1. protected native Object clone()  
  2.                 throws CloneNotSupportedException  

Because all classes are derived from Object, the access modifier protected makes Object clone() available to any method in a class declaration but prevent it from being called by any instance of a class. For a class to make cloning available for its objects, the class must implement a public version of clone() and implement the Cloneable interface : 

- Cloneable class declaration :
  1. public class Time24 implements Cloneable{  
  2. ...  
  3.     public Object clone(){  
  4.         ... //public clone() method  
  5.     }  
  6. }  

Cloneable is an interesting interface. It has no methods and only indicates to the Object clone() method that it is legal for that method to make a field-for-field copy of instances of the class. A method throws the CloneNotSupportedException if it calls clone() for an object whose class type doesn't implement the Cloneable interface. 

Cloning Time24 Objects : 
We illustrate cloning by adding the capability to the Time24 class. The declaration of clone() in the class illustrates how we use the Object method clone() to copy the instance variables hour and minute and thus create a new object. The class header adds Cloneable to the implementation list. The Time24 clone() method calls the Object method clone() using the syntax super.clone() : 

- Cloneable Time24 class :
  1. public class Time24 implements Cloneable{  
  2. ...  
  3.     public Object clone(){  
  4.         Object copy = null;  
  5.         try{  
  6.             copy = (Time24)super.clone();  
  7.         }catch(CloneNotSupportedException cnse){}  
  8.         return copy;  
  9.     }  
  10. ...  
  11. }  

Let us use Time24 clone() to create a new and separated object. The Time24 object tA is 8:30 and the clone is object tB : 

  1. Time24 tA = new Time24(8,24), tB;  
  2. tB = (Time24)tA.clone();  
The cloning process produces a new object tB with data fields for hour and minute and copies the corresponding fields from tA to tB : 
 
The Time24 class has only primitive data fields. In a clone, all of the data has been duplicated. This is termed a deep copy. Updates to the original object or to its clone do not affect the other object. For instance, look what happens whan addTime() advances tA by 15 and tB by 45 minutes : 

  1. tA.addTime(15);  // tA is now 8:45 (8:30+0:15)  
  2. tB.addTime(45);  // tB is now 9:15 (8:30+0:45)  

Cloning Reference Variables : 
When a class has a reference variable, cloning still produces a separated object. However, the original object and the copy are not independent in the sense that an update to one may also affect the other. An example illustrates this idea. The class CloneRef has a primite integer variable n and a Time24 reference variable t as data fields. The cloning algorithm for the Time24 class applies to the CloneRef class : 

- CloneRef class :
  1. public class CloneRef implements Cloneable{  
  2.     private int n;  
  3.     private Time24 t;  
  4.     public CloneRef(int n, int h, int m) {  
  5.         this.n = n;  
  6.         t = new Time24(h,m);  
  7.     }  
  8.       
  9.     public Object clone(){  
  10.         Object copy = null;  
  11.         try{  
  12.             copy = super.clone();  
  13.         }catch(CloneNotSupportedException cnse){}  
  14.         return copy;  
  15.     }  
  16. ...  
  17. }  

To illustrate the relationship between the object and its clone, we add to the CloneRef class methods updateInt(n) and updateTime(m). The former assigns the argument n as the new primitive value; the latter uses addTime() to advance the time of the reference object t by m minutes : 

- updateInt() & updateTime() :
  1. public void updateInt(int n) {  
  2.     this.n = n;  
  3. }  
  4.   
  5. public void updateTime(int m) {  
  6.     t.addTime(m);  
  7. }  

The effect of the update methods on an object and its copy reveal an import property of cloning. Assume crA is CloneRef object with integer value 20 and time 10:15. Using clone(), we create crB as a copy of crA : 

  1. CloneRef crA = new CloneRef(201015);  
  2. CloneRef crB = (CloneRef)crA.clone();  
In the cloning process, crB has data fields for its primitive and reference variables. Each is assigned the value of the corresponding field in crA. Reference variable t is an address and so the field in both crA and crB point at the same Time24 object! 
 
Using method updateInt(55) with the original object crA changes the value of its primitive variable. Using updateTime(30) with either crA OR crB advances time for the instance variable t. The variable t in each CloneRef object points to the same Time24 object, which is now 10:45 : 
 
Cloning an object with reference variables creates two separated objects. However, an update of the reference object affects both the original object and the copy. This is termed a shallow copy. 

Cloning an ArrayList : 
The cloning mechanism that we used for the CloneRef class applies to collection classes. Additional overhead is involved, however, because we want the clone to have its own underlying storage structure that is a copy of the structure for the original collection. Let us see how the cloning mechanism is implemented in the ArrayList class. Recall that an ArrayList object has two instance variables, the integer value listSize and a reference to an array containing the list elements and additional capacity : 

  1. private int listSize;  
  2. private T[] listArr:  
We follow the same pattern for implementing the clone() method in ArrayList and begin by specifying that the class implements Cloneable interface as well as the List interface. The clone() method calls super.clone() to make a copy of the current ArrayList object. The result is a copy of the primitive variable listSize and the reference variable listArr : 
 
This is not quite what we want because the original and cloned variables of listArr references the same list of elements. We solve that problem by allocating in the clone an array of size listSize referenced by a new variable value of listArr, and then by copying the elements from the original list to the new list. The copy is not created with excess capacity that may have been available in the original collection : 

  1. //replace listArr in copy by a new reference to an array  
  2. copy.listArr = (T())new Object[listSize];  
  3. //copy the elements from listArr to copy.listArr  
  4. for(int i=0; i
  5.     copy.listArr[i] = listArr[i];  
Cloning an ArrayList object creates two separated collections with their own lists. Elements can be added or removed from either collection without affecting the other. Be careful, however, when you update objects in the collection. Remember that elements in a collection are references. The original collection and the cloned collection have separate lists of references that point to a common set of objects. For instance, listArr[0] in the original collection references the same object as listArr[0] in the copy. For this reason, we say that the clone of an ArrayList is a shallow copy : 

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!