常範的對象克隆錯誤

一、常見錯誤1# :多次拷貝字符串

測試所不能發現的一個錯誤是生成不可變(immutable)對象的多份拷貝。不可變對象是不可改變的,因此不需要拷貝它。最常用的不可變對象是String。

如果你必須改變一個String對象的內容,你應該使用StringBuffer。下面的代碼會正常工作:

  1. String s = new String ("Text here"); 

但是,這段代碼性能差,而且沒有必要這麼複雜。你還可以用以下的方式來重寫上面的代碼:

  1. String temp = "Text here";   
  2. String s = new String (temp); 

但是這段代碼包含額外的String,並非完全必要。更好的代碼爲:

  1. String s = "Text here"

二、常見錯誤2#: 沒有克隆(clone)返回的對象

封裝(encapsulation)是面向對象編程的重要概念。不幸的是,Java爲不小心打破封裝提供了方便——Java允許返回私有數據的引用(reference)。下面的代碼揭示了這一點:

  1.  
  2. import java.awt.Dimension;   
  3. /***Example class.The x and y values should never*be negative.*/   
  4. public class Example{   
  5.   private Dimension d = new Dimension (00);   
  6.   public Example (){ }  
  7.   /*** Set height and width. Both height and width must be nonnegative * 
  8. or an exception is thrown.*/   
  9.   public synchronized void setValues (int height,int width) throws IllegalArgumentException{   
  10. if (height < 0 || width < 0)   
  11.   throw new IllegalArgumentException();   
  12.   d.height = height;   
  13. d.width = width;   
  14.   }  
  15.   public synchronized Dimension getValues(){   
  16. // Ooops! Breaks encapsulation   
  17. return d;   
  18.   }   

Example類保證了它所存儲的height和width值永遠非負數,試圖使用setValues()方法來設置負值會觸發異常。不幸的是,由於getValues()返回d的引用,而不是d的拷貝,你可以編寫如下的破壞性代碼:

  1. Example ex = new Example();   
  2. Dimension d = ex.getValues();   
  3. d.height = -5;   
  4. d.width = -10

現在,Example對象擁有負值了!如果getValues() 的調用者永遠也不設置返回的Dimension對象的width 和height值,那麼僅憑測試是不可能檢測到這類的錯誤。

不幸的是,隨着時間的推移,客戶代碼可能會改變返回的Dimension對象的值,這個時候,追尋錯誤的根源是件枯燥且費時的事情,尤其是在多線程環境中。

更好的方式是讓getValues()返回拷貝:

  1. public synchronized Dimension getValues(){   
  2. return new Dimension (d.x, d.y);   

現在,Example對象的內部狀態就安全了。調用者可以根據需要改變它所得到的拷貝的狀態,但是要修改Example對象的內部狀態,必須通過setValues()纔可以。

三、常見錯誤3#:不必要的克隆

我們現在知道了get方法應該返回內部數據對象的拷貝,而不是引用。但是,事情沒有絕對:

  1. /*** Example class.The value should never * be negative.*/   
  2. public class Example{   
  3.   private Integer i = new Integer (0);   
  4.   public Example (){ }  
  5.   /*** Set x. x must be nonnegative* or an exception will be thrown*/   
  6.   public synchronized void setValues (int x) throws IllegalArgumentException{   
  7. if (x < 0)   
  8.   throw new IllegalArgumentException();   
  9.   i = new Integer (x);   
  10.   }  
  11.   public synchronized Integer getValue(){   
  12. // We can’t clone Integers so we makea copy this way.   
  13. return new Integer (i.intValue());   
  14.   }   

這段代碼是安全的,但是就象在錯誤1#那樣,又作了多餘的工作。Integer對象,就象String對象那樣,一旦被創建就是不可變的。因此,返回內部Integer對象,而不是它的拷貝,也是安全的。

方法getValue()應該被寫爲:

  1. public synchronized Integer getValue(){   
  2. // ’i’ is immutable, so it is safe to return it instead of a copy.   
  3. return i;   

Java程序比C++程序包含更多的不可變對象。JDK 所提供的若干不可變類包括:

  • Boolean
  • Byte
  • Character
  • Class
  • Double
  • Float
  • Integer
  • Long
  • Short
  • String
  • 大部分的Exception的子類

四、常見錯誤4# :自編代碼來拷貝數組

Java允許你克隆數組,但是開發者通常會錯誤地編寫如下的代碼,問題在於如下的循環用三行做的事情,如果採用Object的clone方法用一行就可以完成:

  1. public class Example{   
  2. private int[] copy;   
  3. /*** Save a copy of ’data’. ’data’ cannot be null.*/   
  4. public void saveCopy (int[] data){   
  5. copy = new int[data.length];   
  6. for (int i = 0; i < copy.length; ++i)   
  7. copy[i] = data[i];   
  8. }   

這段代碼是正確的,但卻不必要地複雜。saveCopy()的一個更好的實現是:

  1. void saveCopy (int[] data){   
  2. try{   
  3. copy = (int[])data.clone();   
  4. }catch (CloneNotSupportedException e){   
  5. // Can’t get here. }   

如果你經常克隆數組,編寫如下的一個工具方法會是個好主意:

  1. static int[] cloneArray (int[] data){   
  2. try{   
  3. return(int[])data.clone();   
  4. }catch(CloneNotSupportedException e){   
  5. // Can’t get here.   
  6. }   

這樣的話,我們的saveCopy看起來就更簡潔了:

  1. void saveCopy (int[] data){   
  2. copy = cloneArray ( data); } 

五、常見錯誤5#:拷貝錯誤的數據

有時候程序員知道必須返回一個拷貝,但是卻不小心拷貝了錯誤的數據。由於僅僅做了部分的數據拷貝工作,下面的代碼與程序員的意圖有偏差:

  1. import java.awt.Dimension;   
  2. /*** Example class. The height and width values should never * be   
  3. negative. */   
  4. public class Example{   
  5. static final public int TOTAL_VALUES = 10;   
  6. private Dimension[] d = new Dimension[TOTAL_VALUES];   
  7. public Example (){ }  
  8. /*** Set height and width. Both height and width must be nonnegative * or
  9.  an exception will be thrown. */   
  10. public synchronized void setValues (int index, int height, int width) 
  11. throws IllegalArgumentException{   
  12. if (height < 0 || width < 0)   
  13. throw new IllegalArgumentException();   
  14. if (d[index] == null)   
  15. d[index] = new Dimension();   
  16. d[index].height = height;   
  17. d[index].width = width;   
  18. }  
  19. public synchronized Dimension[] getValues()   
  20. throws CloneNotSupportedException{   
  21. return (Dimension[])d.clone();   
  22. }   

這兒的問題在於getValues()方法僅僅克隆了數組,而沒有克隆數組中包含的Dimension對象,因此,雖然調用者無法改變內部的數組使其元素指向不同的Dimension對象,但是調用者卻可以改變內部的數組元素(也就是Dimension對象)的內容。方法getValues()的更好版本爲:

  1. public synchronized Dimension[] getValues() throws CloneNotSupportedException{   
  2. Dimension[] copy = (Dimension[])d.clone();   
  3. for (int i = 0; i < copy.length; ++i){   
  4. // NOTE: Dimension isn’t cloneable.   
  5. if (d != null)   
  6. copy[i] = new Dimension (d[i].height, d[i].width);   
  7. }  
  8. return copy;   

在克隆原子類型數據的多維數組的時候,也會犯類似的錯誤。原子類型包括int,float等。簡單的克隆int型的一維數組是正確的,如下所示:

  1. public void store (int[] data) throws CloneNotSupportedException{   
  2. this.data = (int[])data.clone();   
  3. // OK   

拷貝int型的二維數組更復雜些。Java沒有int型的二維數組,因此一個int型的二維數組實際上是一個這樣的一維數組:它的類型爲int[]。簡單的克隆int[][]型的數組會犯與上面例子中getValues()方法第一版本同樣的錯誤,因此應該避免這麼做。下面的例子演示了在克隆int型二維數組時錯誤的和正確的做法:

  1. public void wrongStore (int[][] data) throws CloneNotSupportedException{   
  2.   this.data = (int[][])data.clone(); // Not OK!   
  3. }  
  4. public void rightStore (int[][] data){   
  5.   // OK!   
  6.   this.data = (int[][])data.clone();   
  7.   for (int i = 0; i < data.length; ++i){   
  8. if (data != null)   
  9.   this.data[i] = (int[])data[i].clone();   
  10.   }   
以上都是轉載的東西,作爲提醒自己以後注意點東西,最後加點自己的看法,雖然膚淺
對於上面的#2, 3, 5 點,我認爲主要問題就是getValue() 獲取的是對象引用, 在Effective Java 上面有比較詳細的講述,可不可寫一個工具類,實現對象的深度拷貝呢,這樣這些問題就不存在了。
對於#4, Arrays已經存在這樣的拷貝數組方法了,可以不去發明新的輪子吧.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章