1. 程式人生 > >java中可定製的序列化過程 writeObject與readObject

java中可定製的序列化過程 writeObject與readObject

在Java中使用Serialization相當簡單。如果你有一些物件想要進行序列化,你只需實現Serializable介面。然後,你可以使用ObjectOutputStream將該物件儲存至檔案或傳送到其他主機。所有的non-transient和non-static欄位都將被序列化,並且由反序列化重構造出一模一樣的物件聯絡圖(譬如許多引用都指向該物件)。但有時你可能想實現你自己的物件序列化和反序列化。那麼你可以在某些特定情形下得到更多的控制。來看下面的簡單例子。 
Java程式碼  收藏程式碼
  1. class SessionDTO implements Serializable {  
  2.     private static final long serialVersionUID = 1L;  
  3.     private int data; // Stores session data  
  4.     // Session activation time (creation, deserialization)  
  5.     private long activationTime;   
  6.     public SessionDTO(int data) {  
  7.         this
    .data = data;  
  8.         this.activationTime = System.currentTimeMillis();  
  9.     }  
  10.     public int getData() {  
  11.         return data;  
  12.     }  
  13.     public long getActivationTime() {  
  14.         return activationTime;  
  15.     }  
  16. }  

以下是序列化上述class到檔案和其反序列化的主函式。 
Java程式碼  收藏程式碼
  1. public class SerializeTester implements
     Serializable {  
  2.     public static void main(String... strings) throws Exception {  
  3.         File file = new File("out.ser");  
  4.         ObjectOutputStream oos = new ObjectOutputStream(  
  5.             new FileOutputStream(file));  
  6.         SessionDTO dto = new SessionDTO(1);  
  7.         oos.writeObject(dto);  
  8.         oos.close();  
  9.         ObjectInputStream ois = new ObjectInputStream(  
  10.             new FileInputStream(file));  
  11.         SessionDTO dto = (SessionDTO) ois.readObject();  
  12.         System.out.println("data : " + dto.getData()  
  13.             + " activation time : " + dto.getActivationTime());  
  14.         ois.close();  
  15.     }  
  16. }  

類SessionDTO展現的是要在兩個伺服器之間傳輸的session。它包含了一些資訊在欄位data上,該欄位需要被序列化。但是它還有另外一個欄位activationTime,該欄位應該是session物件第一次出現在任意伺服器上的時間。它不在我們想要傳輸的資訊之列。這個欄位應該在反序列化之後在賦值。進一步來說,沒必要把它放在stream中在伺服器中傳遞,因為它佔據了不必要的空間。 

解決這種情況可以使用writeObject和readObject。有可能你們有一些人沒有聽說過它們,那是因為它們在許多Java書籍中給忽略了,而且它們們也不是眾多流行Java考試的一部分。讓我們用這些方法來重寫SessionDTO: 
Java程式碼  收藏程式碼
  1. class SessionDTO implements Serializable {  
  2.     private static final long serialVersionUID = 1L;  
  3.     private transient int data; // Stores session data  
  4.     //Session activation time (creation, deserialization)  
  5.     private transient long activationTime;   
  6.     public SessionDTO(int data) {  
  7.         this.data = data;  
  8.         this.activationTime = System.currentTimeMillis();  
  9.     }  
  10.     private void writeObject(ObjectOutputStream oos) throws IOException {  
  11.         oos.defaultWriteObject();  
  12.         oos.writeInt(data);  
  13.         System.out.println("session serialized");  
  14.     }  
  15.     private void readObject(ObjectInputStream ois) throws IOException,  
  16.             ClassNotFoundException {  
  17.         ois.defaultReadObject();  
  18.         data = ois.readInt();  
  19.         activationTime = System.currentTimeMillis();  
  20.         System.out.println("session deserialized");  
  21.     }  
  22.     public int getData() {  
  23.         return data;  
  24.     }  
  25.     public long getActivationTime() {  
  26.         return activationTime;  
  27.     }  
  28. }  

方法writeObject處理物件的序列化。如果宣告該方法,它將會被ObjectOutputStream呼叫而不是預設的序列化程序。如果你是第一次看見它,你會很驚奇儘管它們被外部類呼叫但事實上這是兩個private的方法。並且它們既不存在於java.lang.Object,也沒有在Serializable中宣告。那麼ObjectOutputStream如何使用它們的呢?這個嗎,ObjectOutputStream使用了反射來尋找是否聲明瞭這兩個方法。因為ObjectOutputStream使用getPrivateMethod,所以這些方法不得不被宣告為priate以至於供ObjectOutputStream來使用。 

在兩個方法的開始處,你會發現呼叫了defaultWriteObject()和defaultReadObject()。它們做的是預設的序列化程序,就像寫/讀所有的non-transient和 non-static欄位(但他們不會去做serialVersionUID的檢查).通常說來,所有我們想要自己處理的欄位都應該宣告為transient。這樣的話,defaultWriteObject/defaultReadObject便可以專注於其餘欄位,而我們則可為這些特定的欄位(譯者:指transient)定製序列化。使用那兩個預設的方法並不是強制的,而是給予了處理複雜應用時更多的靈活性。 

你可以從這裡這裡讀到更多有關於序列化的知識。 

自己再補充一些: 

1.Write的順序和read的順序需要對應,譬如有多個欄位都用wirteInt一一寫入流中,那麼readInt需要按照順序將其賦值; 

2.Externalizable,該介面是繼承於Serializable ,所以實現序列化有兩種方式。區別在於Externalizable多聲明瞭兩個方法readExternal和writeExternal,子類必須實現二者。Serializable是內建支援的也就是直接implement即可,但Externalizable的實現類必須提供readExternal和writeExternal實現。對於Serializable來說,Java自己建立物件圖和欄位進行物件序列化,可能會佔用更多空間。而Externalizable則完全需要程式設計師自己控制如何寫/讀,麻煩但可以有效控制序列化的儲存的內容。 

3.正如Effectvie Java中提到的,序列化就如同另外一個建構函式,只不過是有由stream進行建立的。如果欄位有一些條件限制的,特別是非可變的類定義了可變的欄位會反序列化可能會有問題。可以在readObject方法中新增條件限制,也可以在readResolve中做。參考56條“保護性的編寫readObject”和“提供一個readResolve方法”。 

4.當有非常複雜的物件需要提供deep clone時,可以考慮將其宣告為可序列化,不過缺點也顯而易見,效能開銷。