上一個建議說了對象的淺拷貝問題,實現Cloneable接口就具備了拷貝能力,那我們來思考這樣一個問題:如果一個項目中有大量的對象是通過拷貝生成的,那我們該如何處理?每個類都寫一個clone方法,並且還要深拷貝?想想看這是何等巨大的工作量呀,是否有更好的方法呢?
其實,可以通過序列化方式來處理,在內存中通過字節流的拷貝來實現,也就是把母對像寫到一個字節流中,再從字節流中將其讀出來,這樣就可以重建一個新對象了,該新對象與母對像之間不存在引用共享的問題,也就相當於深拷貝了一個新對象,代碼如下:
public class CloneUtils{
//拷貝一個對像
@SuppressWarnings("unchecked")
public static<T extends Serializable>T clone(T obj){
//拷貝產生的對象
T clonedObj=null;
try{
//讀取對像字節數據
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
//分配內存空間,寫入原始對象,生成新對像
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bais);
//返回新對象,並做類型轉換
clonedObj=(T)ois.readObject();
ois.close();
}catch(Exception e){
e.printStackTrace();
}
return clonedObj;
}
}
此工具類要求被拷貝的對象必須實現Serializable接口,否則是沒辦法拷貝的(當然,使用反射那是另外一種技巧),上一個建議中的例子只要稍微修改一下即可實現深拷貝,代碼如下:
class Person implements Serializable{
private static final long serialVersionUID=1611293231L;
/*刪除掉clone方法,其他代碼保持不變*/
}
被拷貝的類只要實現Serializable這個標誌性接口即可,不需要任何實現,當然serialVersionUID常量還是要加上去的,然後我們就可以通過CloneUtils工具進行對象的深拷貝了。用此方法進行對像拷貝時需要注意兩點:
(1)對象的內部屬性都是可序列化的
如果有內部屬性不可序列化,則會拋出序列化異常,這會讓調試者很納悶:生成一個對像怎麼會出現序列化異常呢?從這一點來考慮,也需要把CloneUtils工具的異常進行細化處理。
(2)注意方法和屬性的特殊修飾符
比如final、static變量的序列化問題會被引入到對像拷貝中來(參考第1章),這點需要特別注意,同時transient變量(瞬態變量,不進行序列化的變量)也會影響到拷貝的效果。
當然,採用序列化方式拷貝時還有一個更簡單的辦法,即使用Apache下的commons工具包中的SerializationUtils類,直接使用更加簡潔方便。