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

Java 序列化深度解析:當序列化遭遇繼承 — 父類未實現 Serializable 的解決之道

     編輯:Java 程式設計入門

基礎規則回顧

父類實作 Serializable → 子類自動可序列化 (無需額外宣告)。

class SuperC implements Serializable { int supervalue; }
class SubC extends SuperC { int subvalue; } // 自動可序列化

挑戰:父類無法/不願實作 Serializable

常見場景:

  • 父類為第三方庫類別,不可修改
  • 父類為抽象基類,設計意圖不強制所有子類可序列化
  • 父類包含非序列化資源 (Socket, Thread, 連線池)

Java 規範要求 (Java Object Serialization Spec)

為讓非序列化父類的子類可序列化,子類必須:
  1. 父類具有 可存取的無參數建構子 (public/protected)
  2. 子類 自行負責 儲存/還原父類的 public/protected/package-private 欄位
否則執行時拋出 InvalidClassException。

實作步驟:自定義序列化邏輯

1. 父類 (不可修改,假設有無參建構子)

class SuperC { // 未實作 Serializable
    int supervalue;
    public SuperC() {} // 必須有無參建構子
    public SuperC(int v) { supervalue = v; }
}

2. 子類實作 Serializable + writeObject/readObject

class SubC extends SuperC implements Serializable {
    int subvalue;
    
    public SubC(int superV, int subV) {
        super(superV);
        this.subvalue = subV;
    }
    
    // 自定義序列化:存檔父類欄位
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 先存子類自身欄位 (subvalue)
        out.writeInt(supervalue); // 手動存父類欄位
    }
    
    // 自定義反序列化:還原父類欄位
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 先還原子類自身欄位
        supervalue = in.readInt(); // 手動還原父類欄位
        // 注意:父類無參建構子已由反序列化機制自動調用
    }
}

3. 驗證測試

public class TestInheritanceSerialization {
    public static void main(String[] args) {
        SubC obj = new SubC(100, 200);
        System.out.println("原始: super=" + obj.supervalue + ", sub=" + obj.subvalue);
        
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"))) {
            
            oos.writeObject(obj);
            SubC restored = (SubC) ois.readObject();
            System.out.println("還原: super=" + restored.supervalue + ", sub=" + restored.subvalue);
        } catch (Exception e) { e.printStackTrace(); }
    }
}

輸出:

原始: super=100, sub=200
還原: super=100, sub=200

進階:Externalizable 完全掌控

實作 Externalizable 介面,必須實作:

public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

優點:完全控制序列化格式、版本演進彈性大、可跳過 defaultWriteObject 開銷。
缺點:父類欄位、超類鏈、transient 欄位皆需手動處理,維護成本高。

關鍵陷阱與最佳實踐

陷阱後果對策
父類無無參建構子反序列化失敗 (InstantiationException)若可修改父類 → 加入無參建構子;不可修改 → 無法序列化該繼承鏈
忘記呼叫 defaultWriteObject/ReadObject子類自身欄位遺失永遠先呼叫 default 方法,再手動處理父類欄位
父類欄位為 private子類無法存取只能序列化 public/protected/package-private 欄位;private 需父類提供 getter/setter
靜態/transient 欄位不參與序列化預期行為,無需處理
序列化版本不一致InvalidClassException (serialVersionUID)明確宣告 private static final long serialVersionUID = 1L;

現代替代方案

  • JSON 序列化 (Jackson/Gson):無繼承限制、跨語言、人類可讀、版本容忍度高
  • Protocol Buffers / Avro:Schema 驅動、高效能、強版本管理
  • JPA/Hibernate:持久化替代序列化

總結

  1. 父類實作 Serializable → 子類零成本繼承序列化能力
  2. 父類未實作 → 子類須:(a) 確保父類有無參建構子 (b) 實作 writeObject/readObject 手動處理父類欄位
  3. 嚴謹宣告 serialVersionUID
  4. 新專案優先考慮 JSON/Protobuf,避開 Java 原生序列化坑點

理解繼承與序列化的交互規則,是處理舊系統遷移、分散式傳輸、快取儲存的關鍵技能。

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