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

Java 自定義序列化深度解析:writeObject/readObject、繼承陷阱與單例保護

     編輯:Java 相關知識

問題場景:父類非可序列化導致狀態丟失

子類實現 Serializable,父類未實現且包含狀態字段。預期反序列化後父類字段保持原值,實際卻重置為默認值(0/null)。

class AbstractSerializeDemo {
    private int x, y;
    public void init(int x, int y) { this.x = x; this.y = y; }
    public int getX() { return x; }
    public int getY() { return y; }
    public void printXY() { System.out.println(\"x:\" + x + \";y:\" + y); }
}

public class SerializeDemo extends AbstractSerializeDemo implements Serializable {
    private int z;
    public SerializeDemo() { super.init(10, 50); z = 100; }
    public void printZ() { super.printXY(); System.out.println(\"z:\" + z); }
    // ... 測試代碼 ...
}

輸出:

x:10;y:50
z:100
x:0;y:0   <-- 父類狀態丟失!
z:100

根因

反序列化時,JVM 嘗試調用父類的無參構造器初始化父類部分。父類未實現 Serializable,其字段不在序列化流中,故重置為默認值。

解決方案:自定義 writeObject / readObject

在子類中實現私有方法,手動寫入/讀取父類字段:

private void writeObject(ObjectOutputStream os) throws IOException {
    os.defaultWriteObject();  // 先執行默認序列化(子類字段 z)
    os.writeInt(getX());      // 手動寫入父類字段 x
    os.writeInt(getY());      // 手動寫入父類字段 y
}

private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
    is.defaultReadObject();   // 先執行默認反序列化(子類字段 z)
    int x = is.readInt();     // 按寫入順序讀取
    int y = is.readInt();
    super.init(x, y);         // 調用父類初始化方法恢復狀態
}

再次運行,輸出符合預期:

x:10;y:50
z:100
x:10;y:50
z:100

進階機制:writeReplace / readResolve

  • writeReplace():序列化前替換對象(返回代理對象)。
  • readResolve():反序列化後替換對象(返回規範實例)。

典型應用:保護單例模式防止反序列化產生多實例。

private Object readResolve() {
    return INSTANCE;  // 返回單例實例
}
private Object writeReplace() {
    return INSTANCE;
}

調用順序:writeReplace → writeObject → (流傳輸) → readObject → readResolve。

註:Java Enum 內部即通過 readResolve 保證單例,建議優先使用 Enum 實現單例。

最佳實踐總結

  1. 父類非可序列化但有狀態 → 子類實現 writeObject/readObject 手動處理父類字段。
  2. 需控制序列化形式/單例保護 → 實現 writeReplace/readResolve。
  3. 所有自定義序列化方法必須聲明為 private,JVM 通過反射調用。
  4. 調用 defaultWriteObject/defaultReadObject 保留默認處理邏輯,僅補充特殊字段。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved