首頁  >  文章  >  Java  >  Java中物件序列化與反序列化詳解

Java中物件序列化與反序列化詳解

高洛峰
高洛峰原創
2017-01-18 11:22:161367瀏覽

本文實例講述了Java中物件序列化與反序列化。分享給大家供大家參考。具體如下:

一、簡介

物件序列化(Serializable)是指將物件轉換為位元組序列的過程,而反序列化則是根據位元組序列恢復物件的過程。

序列化一般用於以下場景:

1.永久性保存對象,保存對象的位元組序列到本地文件中;
2.透過序列化對像在網路中傳遞對象;
3.透過序列化在進程間傳遞對象。

物件所屬的類別必須實作Serializable或是Externalizable介面才能被序列化。對實現了Serializable接口的類,其序列化與反序列化採用預設的序列化方式,Externalizable接口是繼承了Serializable接口的接口,是對Serializable的擴展,實現了Externalizable接口的類完全自己控制序列化與反序列化行為。

Java.io.ObjectOutputStream代表物件輸出流,其方法writeObject(Object obj)可以實現物件的序列化,將得到的位元組序列寫入目標輸出流。

Java.io.ObjectInputStream代表物件輸入流,其readObject()方法能從來源輸入流中讀取位元組序列,將其反序列化為對象,並將其傳回。

二、序列化的幾種方式

假設定義了一個Customer類,根據Customer實現序列化方式的不同,可能有以下幾種序列化方式:

1.實作Serializable,未定義readObject和writeObject方法

ObjectOutputStream使用JDK預設方式對Customer物件的非transient的實例變數進行序列化;
ObjectInputStream使用JDK預設方式對Customer物件的非transient的實例變數進行反序列化。

2.實作Serializable,並定義了readObject和writeObject方法

ObjectOutputStream呼叫Customer類別的writeObject(ObjectOutputStream out)方法對Customer物件的非transient的實例變數進行序列化;
ObjectInputStreamStreamAustomerStream inputStream版本的調用(CustomerStream inObject(CustomerStreamInputStream)。 )方法對Customer物件的非transient的實例變數進行反序列化。

3.實作Externalizable,定義readExternal和writeExternal方法

ObjectOutputStream呼叫Customer類別的writeExternal方法對Customer物件的非transient實例變數進行序列化;
ObjectInputStream首先透過Customer物件類別的無物件函數進行一個序列化;用readExternal方法對Customer物件的非transient實例變數進行反序列化。

三、Serializable介面

類別透過實作 java.io.Serializable 介面以啟用其序列化功能。未實作此介面的類別將無法使其任何狀態序列化或反序列化。可序列化類別的所有子類型本身都是可序列化的。序列化介面沒有方法或字段,僅用於標識可序列化的語義。

在反序列化過程中,將使用該類別的公用或受保護的無參數建構方法初始化不可序列化類別的欄位。可序列化的子類別必須能夠存取無參數構造方法。可序列化子類別的欄位將從該流中恢復。

當遍歷一個類別視圖時,可能會遇到不支援 Serializable 介面的物件。在此情況下,將拋出 NotSerializableException,並將標識不可序列化物件的類別。

1.準確簽名

在序列化和反序列化過程中需要特殊處理的類別必須使用下列準確簽名來實現特殊方法:

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 方法負責寫入特定類別的物件的狀態,以便將它還原為對應的。透過呼叫 out.defaultWriteObject 可以呼叫保存 Object 的欄位的預設機制。該方法本身不需要涉及屬於其超類別或子類別的狀態。透過使用 writeObject 方法或使用 DataOutput 支援的用於基本資料類型的方法將各個欄位寫入 ObjectOutputStream,狀態可以被保存。

readObject 方法負責從流中讀取並還原類別欄位。它可以呼叫 in.defaultReadObject 來呼叫預設機制,以還原物件的非靜態和非瞬態欄位。 defaultReadObject 方法使用流中的資訊來指派流中透過目前物件中對應指定欄位所儲存的物件的欄位。這用於處理類別演化後需要新增欄位的情況。該方法本身不需要涉及屬於其超類別或子類別的狀態。透過使用 writeObject 方法或使用 DataOutput 支援的用於基本資料類型的方法將各個欄位寫入 ObjectOutputStream,狀態可以被保存。

在序列化流不列出給定類別作為將被反序列化物件的超類別的情況下,readObjectNoData 方法負責初始化特定類別的物件狀態。這在接收方使用的反序列化實例類別的版本不同於發送方,且接收者版本擴展的類別不是發送者版本擴展的類別時發生。在序列化流已經被篡改時也將發生;因此,不管源流是「敵意的」還是不完整的,readObjectNoData 方法都可以用來正確地初始化反序列化的物件。

將物件寫入流時需要指定要使用的替代物件的可序列化類,應使用準確的簽章來實作此特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此writeReplace方法將由序列化調用,前提是如果此方法存在,而且它可以透過被序列化物件的類別中定義的一個方法來存取。因此,此方法可以擁有私有 (private)、受保護的(protected) 和套件私有 (package-private) 存取。子類別對此方法的存取遵循 java 存取規則。

在從流中讀取類別的一個實例時需要指定替代的類別應使用的準確簽章來實作此特殊方法。

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此readResolve方法遵循與writeReplace相同的呼叫規則和存取規則。
如果一個類別定義了readResolve方法,那麼在反序列化的最後將呼叫readResolve方法,該方法傳回的物件為反序列化的最終結果。

2.serialVersionUID

序列化運行時使用一個稱為serialVersionUID 的版本號與每個可序列化類別相關聯,該序號在反序列化過程中用於驗證序列化物件的發送者和接收者是否為該物件載入了與序列化相容的類別。如果接收者載入的該物件的類別的 serialVersionUID 與對應的發送者的類別的版本號碼不同,則反序列化將會導致 InvalidClassException。可序列化類別可以透過宣告名為"serialVersionUID" 的欄位(此欄位必須是靜態(static)、最終(final) 的long 型欄位)明確宣告其自己的serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類別未明確聲明serialVersionUID,則序列化運行時將基於該類別的各個方面計算該類別的預設serialVersionUID 值,如“Java(TM) 物件序列化規範”中所述。不過,強烈建議所有可序列化類別都明確聲明serialVersionUID 值,原因是計算預設的serialVersionUID 對類別的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的InvalidClassException。因此,為確保 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類別必須宣告一個明確的 serialVersionUID 值。也強烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用於直接聲明類別 -- serialVersionUID 欄位作為繼承成員沒有用處。陣列類別不能宣告一個明確的 serialVersionUID,因此它們總是具有預設的計算值,但是陣列類別沒有符合 serialVersionUID 值的要求。

3.Externalizable介面

Externalizable是Serailizable的擴展,實現Externalizable介面的類別其序列化有以下特點:
序列化時呼叫類別的方法writeExternal,反序列化呼叫readExternal方法;
序列化時呼叫類別的方法writeExternal,反序列化呼叫readExternal方法;

序列化時在執行反序列化時先呼叫類別的無參數建構函數,這點與預設的反序列化是不同的,因此對實作Externalizable介面來實現序列化的類別而言,必須提供一個public的無參數建構函數,否則在反序列化時將出現異常。

四、總結

如果採用預設的序列化方式,只要讓一個類別實現Serializable接口,其實例就可以被序列化。通常,專門為繼承而設計的類別應該盡量不要實作Serializable接口,因為一旦父類別實作了Serializable接口,其所有子類別也都是可序列化的了。

預設的序列化方式的不足之處:


1.直接對物件的不宜對外公開的敏感資料進行序列化,這是不安全的;
2.不會檢查物件的成員變數是否符合正確的約束條件,有可能被篡改資料而導致運行異常;
3.需要對物件圖做遞歸遍歷,如果物件圖很複雜,會消耗很多資源,設定引起Java虛擬機的堆疊溢出;

4.使類別的介面被類別的內部實作約束,制約類別的升級與維護。

透過實作Serializable介面的private類型的writeObject()和readObject(),或是實作Externalizable介面,並實作writeExternal()與readExternal()方法,並提供public型別的無參數構造函數兩種方式來控制序列化過程可以有效規避預設序列化方式的不足之處。

希望本文所述對大家的java程式設計有幫助。

更多Java中物件序列化與反序列化詳解相關文章請關注PHP中文網! 🎜
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn