本文實例講述了Java中對象序列化與反序列化。分享給大家供大家參考。具體如下:
一、簡介
對象序列化(Serializable)是指將對象轉(zhuǎn)換為字節(jié)序列的過程,而反序列化則是根據(jù)字節(jié)序列恢復(fù)對象的過程。
序列化一般用于以下場景:
1.永久性保存對象,保存對象的字節(jié)序列到本地文件中;
2.通過序列化對象在網(wǎng)絡(luò)中傳遞對象;
3.通過序列化在進程間傳遞對象。
對象所屬的類必須實現(xiàn)Serializable或是Externalizable接口才能被序列化。對實現(xiàn)了Serializable接口的類,其序列化與反序列化采用默認的序列化方式,Externalizable接口是繼承了Serializable接口的接口,是對Serializable的擴展,實現(xiàn)了Externalizable接口的類完全自己控制序列化與反序列化行為。
Java.io.ObjectOutputStream代表對象輸出流,其方法writeObject(Object obj)可以實現(xiàn)對象的序列化,將得到的字節(jié)序列寫到目標輸出流中。
Java.io.ObjectInputStream代表對象輸入流,其readObject()方法能從源輸入流中讀取字節(jié)序列,將其反序列化為對象,并將其返回。
二、序列化的幾種方式
假設(shè)定義了一個Customer類,根據(jù)Customer實現(xiàn)序列化方式的不同,可能有以下幾種序列化方式:
1.實現(xiàn)Serializable,未定義readObject和writeObject方法
ObjectOutputStream使用JDK默認方式對Customer對象的非transient的實例變量進行序列化;
ObjectInputStream使用JDK默認方式對Customer對象的非transient的實例變量進行反序列化。
2.實現(xiàn)Serializable,并定義了readObject和writeObject方法
ObjectOutputStream調(diào)用Customer類的writeObject(ObjectOutputStream out)方法對Customer對象的非transient的實例變量進行序列化;
ObjectInputStream調(diào)用Customer類的readObject(ObjectInputStream in)方法對Customer對象的非transient的實例變量進行反序列化。
3.實現(xiàn)Externalizable,定義readExternal和writeExternal方法
ObjectOutputStream調(diào)用Customer類的writeExternal方法對Customer對象的非transient實例變量進行序列化;
ObjectInputStream首先通過Customer類的無參數(shù)構(gòu)造函數(shù)實例化一個對象,再用readExternal方法對Customer對象的非transient實例變量進行反序列化。
三、Serializable接口
類通過實現(xiàn) java.io.Serializable 接口以啟用其序列化功能。未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。
在反序列化過程中,將使用該類的公用或受保護的無參數(shù)構(gòu)造方法初始化不可序列化類的字段。可序列化的子類必須能夠訪問無參數(shù)構(gòu)造方法。可序列化子類的字段將從該流中恢復(fù)。
當遍歷一個類視圖時,可能會遇到不支持 Serializable 接口的對象。在此情況下,將拋出 NotSerializableException,并將標識不可序列化對象的類。
1.準確簽名
在序列化和反序列化過程中需要特殊處理的類必須使用下列準確簽名來實現(xiàn)特殊方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
writeObject 方法負責寫入特定類的對象的狀態(tài),以便相應(yīng)的 readObject 方法可以恢復(fù)它。通過調(diào)用 out.defaultWriteObject 可以調(diào)用保存 Object 的字段的默認機制。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入 ObjectOutputStream,狀態(tài)可以被保存。
readObject 方法負責從流中讀取并恢復(fù)類字段。它可以調(diào)用 in.defaultReadObject 來調(diào)用默認機制,以恢復(fù)對象的非靜態(tài)和非瞬態(tài)字段。defaultReadObject 方法使用流中的信息來分配流中通過當前對象中相應(yīng)指定字段保存的對象的字段。這用于處理類演化后需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入 ObjectOutputStream,狀態(tài)可以被保存。
在序列化流不列出給定類作為將被反序列化對象的超類的情況下,readObjectNoData 方法負責初始化特定類的對象狀態(tài)。這在接收方使用的反序列化實例類的版本不同于發(fā)送方,并且接收者版本擴展的類不是發(fā)送者版本擴展的類時發(fā)生。在序列化流已經(jīng)被篡改時也將發(fā)生;因此,不管源流是“敵意的”還是不完整的,readObjectNoData 方法都可以用來正確地初始化反序列化的對象。
將對象寫入流時需要指定要使用的替代對象的可序列化類,應(yīng)使用準確的簽名來實現(xiàn)此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此writeReplace方法將由序列化調(diào)用,前提是如果此方法存在,而且它可以通過被序列化對象的類中定義的一個方法訪問。因此,該方法可以擁有私有 (private)、受保護的(protected) 和包私有 (package-private) 訪問。子類對此方法的訪問遵循 java 訪問規(guī)則。
在從流中讀取類的一個實例時需要指定替代的類應(yīng)使用的準確簽名來實現(xiàn)此特殊方法。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此readResolve方法遵循與writeReplace相同的調(diào)用規(guī)則和訪問規(guī)則。
如果一個類定義了readResolve方法,那么在反序列化的最后將調(diào)用readResolve方法,該方法返回的對象為反序列化的最終結(jié)果。
2.serialVersionUID
序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關(guān)聯(lián),該序列號在反序列化過程中用于驗證序列化對象的發(fā)送者和接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應(yīng)的發(fā)送者的類的版本號不同,則反序列化將會導(dǎo)致 InvalidClassException。可序列化類可以通過聲明名為 "serialVersionUID" 的字段(該字段必須是靜態(tài) (static)、最終 (final) 的 long 型字段)顯式聲明其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認 serialVersionUID 值,如“Java(TM) 對象序列化規(guī)范”中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因是計算默認的 serialVersionUID 對類的詳細信息具有較高的敏感性,根據(jù)編譯器實現(xiàn)的不同可能千差萬別,這樣在反序列化過程中可能會導(dǎo)致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現(xiàn)的一致性,序列化類必須聲明一個明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應(yīng)用于直接聲明類 -- serialVersionUID 字段作為繼承成員沒有用處。數(shù)組類不能聲明一個明確的 serialVersionUID,因此它們總是具有默認的計算值,但是數(shù)組類沒有匹配 serialVersionUID 值的要求。
3.Externalizable接口
Externalizable是Serailizable的擴展,實現(xiàn)Externalizable接口的類其序列化有以下特點:
序列化時調(diào)用類的方法writeExternal,反序列化調(diào)用readExternal方法;
在執(zhí)行反序列化時先調(diào)用類的無參數(shù)構(gòu)造函數(shù),這一點與默認的反序列化是不同的,因此對實現(xiàn)Externalizable接口來實現(xiàn)序列化的類而言,必須提供一個public的無參數(shù)構(gòu)造函數(shù),否則在反序列化時將出現(xiàn)異常。
四、總結(jié)
如果采用默認的序列化方式,只要讓一個類實現(xiàn)Serializable接口,其實例就可以被序列化。通常,專門為繼承而設(shè)計的類應(yīng)該盡量不要實現(xiàn)Serializable接口,因為一旦父類實現(xiàn)了Serializable接口,其所有子類也都是可序列化的了。
默認的序列化方式的不足之處:
1.直接對對象的不宜對外公開的敏感數(shù)據(jù)進行序列化,這是不安全的;
2.不會檢查對象的成員變量是否符合正確的約束條件,有可能被篡改數(shù)據(jù)而導(dǎo)致運行異常;
3.需要對對象圖做遞歸遍歷,如果對象圖很復(fù)雜,會消耗很多資源,設(shè)置引起Java虛擬機的堆棧溢出;
4.使類的接口被類的內(nèi)部實現(xiàn)約束,制約類的升級與維護。
通過實現(xiàn)Serializable接口的private類型的writeObject()和readObject(),或是實現(xiàn)Externalizable接口,并實現(xiàn)writeExternal()與readExternal()方法,并提供public類型的無參數(shù)構(gòu)造函數(shù)兩種方式來控制序列化過程可以有效規(guī)避默認序列化方式的不足之處。
希望本文所述對大家的java程序設(shè)計有所幫助。