首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 序列化控制機制深度解析

     編輯:Java 相關知識

預設的 Java 序列化機制雖然簡單易用,但當遇到特殊需求時——例如出於安全考量不想序列化對象的某些部分,或某些子對象在恢復後需要重新創建——就需要更精細的控制。此時實作 Externalizable 介面便能完全掌控序列化與反序列化的具體流程。

Externalizable 介面核心機制

Externalizable 繼承自 Serializable,並新增兩個關鍵方法:writeExternal() 與 readExternal()。序列化與反序列化過程中會自動呼叫這兩個方法,讓開發者執行特殊操作。

關鍵差異:建構子的可見性

以下範例展示 Blip1 與 Blip2 的細微差別:

class Blip1 implements Externalizable {
    public Blip1() { System.out.println("Blip1 Constructor"); }
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip1.writeExternal");
    }
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Blip1.readExternal");
    }
}

class Blip2 implements Externalizable {
    Blip2() { System.out.println("Blip2 Constructor"); } // 非 public
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip2.writeExternal");
    }
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Blip2.readExternal");
    }
}

執行結果顯示 Blip1 成功恢復,但 Blip2 拋出異常。原因在於 Blip2 的建構子非 public,反序列化時 JVM 無法呼叫預設建構子。Externalizable 物件反序列化會先呼叫預設建構子,再呼叫 readExternal(),這與 Serializable 直接從二進制位恢復、不呼叫建構子有本質不同。

完整序列化控制範例

class Blip3 implements Externalizable {
    int i; String s;
    public Blip3() { System.out.println("Blip3 Constructor"); }
    public Blip3(String x, int a) { s = x; i = a; }
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(s); out.writeInt(i); // 必須顯式寫入
    }
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        s = (String)in.readObject(); i = in.readInt(); // 必須顯式讀取
    }
}

若不在 readExternal 中初始化欄位,它們將保持預設值(null 或 0)。繼承 Externalizable 物件時,通常需呼叫基底類別的 writeExternal() 與 readExternal() 以正確處理基底部分。

transient 關鍵字:針對單一欄位的序列化控制

若僅需防止特定敏感欄位(如密碼)被序列化,可使用 transient 關鍵字標記該欄位,意為「自動機制勿需保存/恢復此欄位,我自行處理」。

class Logon implements Serializable {
    private Date date = new Date();
    private String username;
    private transient String password; // 不會被自動序列化
    // ...
}

反序列化後,password 將為 null。注意 transient 僅適用於 Serializable,Externalizable 預設不序列化任何欄位,故無需此關鍵字。

替代方案:自訂 writeObject/readObject

實作 Serializable 並新增 private void writeObject(ObjectOutputStream) 與 private void readObject(ObjectInputStream) 方法,即可接管序列化流程。這兩個方法簽名必須精確:

private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;

雖然它們是 private,但序列化機制會透過反射呼叫。在方法內可呼叫 defaultWriteObject() 與 defaultReadObject() 執行預設行為,再手動處理 transient 欄位:

private void writeObject(ObjectOutputStream stream) throws IOException {
    stream.defaultWriteObject(); // 處理非 transient 欄位
    stream.writeObject(b);       // 手動處理 transient 欄位
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    b = (String)stream.readObject();
}

此方式比 Externalizable 更靈活,可選擇性保留預設序列化行為。

版本控制考量

可序列化類別的版本演進雖受支援,但需深入理解底層機制,建議參考 JDK 文件中的 serialVersionUID 等進階主題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved