1.概述
要實現序列化,則必須實現serializable或Externalizable接口。后者繼承自前者,兩者的區別:實現前者的類可以采用默認的序列化方式。而實現后者的類則完全由自身來控制序列化的行為。
在序列化和反序列化過程中需要特殊處理的類必須使用下列準確簽名來實現特殊方法:
private void writeObject java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
writeObject 方法負責寫入特定類的對象的狀態,以便相應的 readObject 方法可以還原它。通過調用 out.defaultWriteObject 可以調用保存 Object 的字段的默認機制。該方法本身不需要涉及屬于其超類或子類的狀態。狀態是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
readObject 方法負責從流中讀取并還原類字段。它可以調用 in.defaultReadObject 來調用默認機制,以還原對象的非靜態和非瞬態字段。defaultReadObject 方法使用流中的信息來分配流中通過當前對象中相應命名字段保存的對象的字段。這用于處理類發展后需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀態。狀態是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
假定有一個名為Customer的類,它的對象需要序列化,如果Customer類僅僅實現了Serializable接口的類,那么將按照以下方式序列化及反序列化Customer對象:
- ObjectOutputStream采用默認的序列化方式,對Customer對象的非transient的實例變量進行序列化。
- ObjectInputStream采用默認的反序列化方式,對Customer對象的非transient的實例變量進行反序列化。
如果Customer類僅僅實現了Serializable接口的類,并且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out)方法,那么將按照以下方式序列化及反序化Customer對象:
- ObjectOutputStream會調用Customer類的writeObject(ObjectOutputStream out)方法來進行序列化。
- ObjectInputStream會調用Customer類的readObject(ObjectInputStream in)方法來進行反序列化。
如果Customer類實現了Externalizable接口,那么Customer類必須實現readExternal
(ObjectInput in)和writeExternal(ObjectOutput out)方法。在這種情況下,將按照以下方式序列化及反序列化Customer對象:
- ObjectOutputStream會調用Customer類的writeExternal(ObjectOutput out)方法來進行序列化。
- ObjectInputStream先通過Customer類的不帶參數的構造方法創建一個Customer對象,然后調用它的readExternal(ObjectInput int)方法來進行反序列化。
2.實現Serializable接口
ObjectOutputStream只能對實現了Serializable接口的類的對象進行序列化。默認情況下,ObjectOutputStream按照默認方式序列化,這種序列化方式僅僅對對象的非transient的實例變量進行序列化,而不會序列化對象的transient的實例變量,也不會序列化靜態變量。
而當ObjectInputStream按照默認方式反序列化時,有以下特點:
- 如果在內在中對象所屬的類還沒有被加載,那么會先加載并初始化這個類。如果在classpath中不存在相應的類文件,那么會拋出ClassNotFoundException。
- 在反序列化時不會調用類的任何構造方法。
被transient修飾符來修飾的實例變量是不會序列化的,一般用transient來修飾以下類型的的變量:
1) 實例變量不代表對象的固有的內部數據,僅僅代表具有一定邏輯含義的臨時數據。
2) 實例變量表示一些比較敏感的信息,如密碼,出于安全方面的原因,不希望對其序列化。
3) 實例變量需要按照用戶自定義的方式序列化,如經過加密后再序列化。這這種情況下可以將其用transient修飾,然后在writeObject()方法中對其序列化。
2.1序列化對象圖
參見SerializableDemo中的Customer2及Order2類之間的關系。
當通過ObjectOutputStream對象的writeObject(customer)方法序列化Customer2對象時,也會序列化與它關聯的Order2對象。而當通過ObjectInputStream對象的readObject()方法反序列化Customer2對象時,實際上會對整個對象圖反序列化。
如果對象A持有對象B的引用(注意是A持有B的引用),以及間接持有其他對象的引用,則按照默認方式序列化對象A時,會將A以及A持有的以及間接持有的所有對象都序列化。反序列化也是如此。
2.2控制序列化的行為
如果用戶希望控制類的序列化方式,可以在可序列化類中提供以下形式的writeObject()方法和readObject()方法:
private void writeObject java.io.ObjectOutputStream out)throws IOException
private void readObject java.io.ObjectInputStream in)throws IOException,ClassNotFoundException;

一般的做法是,在writeObject()方法中,選留骼ObjectOutputStream的defaultWriteObject()方法,使得對象輸出流先執行默認的序列化操作。
反序列化時,在readObject()方法中,先調用ObjectInputStream的defaultReadObject()方法。
2.3readResolve()方法在童便類中的運用
如果一個類提供了readResolve()方法,那么在執行反序列化操作時,先按照默認方式或者用戶自定義的方式進行反序列化,最后再調用readResolve()方法,該方法返回的對象為反序列化的最終結果。
readResolve()方法應該能夠被類本身、同一個包中的類,或者訪問,因此readResolve()方法的訪問權限可以是pirvate、默認或protected級別。
readResolve()方法用來重新指定反序列化得到的對象,與此對應,Java序列化規范還允許在可序列化類中定義一個writeReplace()方法,用來重新指定被序列化的對象。writeReplace()方法返回一個Object類型的對象,這個返回對象才是真正要被序列化的對象。權限也可以為以上三種之一。
3.實現Externalizalbe接口
Externallizable接口繼承自Serializable接口,如果一個類實現了Externalizable接口,那么將完全由這個類控制自身的序列化行為。Externalizable接口中聲明了兩個方法:
public void writeExternal(ObjectOutput out)throws IOException
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
writeExternal負責序列化操作,readExternal負責反序列化操作。在對實現了Externallizable接口的類的對象進行反序列化時,會先調用類的不帶參數的構造方法,這是有別默認反序列化方式的(見2)。所以實現Externalizable接口的類必須要有不含參數的構造方法。
4.可序列化類的不同版本的序列化兼容性
實例見SerializableDemo里的Customer5的兩個版本。將Customer1.0和SimpleServer放在server端,將Customer2.0和SimpleClient放在Client端,直接運行,將發現拋出錯誤,顯示不兼容,解決辦法是手動將兩個Customer的serialVersionUID設為同一個值,這樣就能兼容。但是這種辦法的能力很有限,當一個類的不同版本的serialVersionUID相同,仍然有可能出現序列化不兼容的情況。因為序列化兼容性不僅取決于serialVersionUID,還取決于類的不同版本的實現細節和序列化細節。
posted on 2010-03-28 16:33
Dreava 閱讀(1181)
評論(0) 編輯 收藏 所屬分類:
Java基礎