父類實作 Serializable → 子類自動可序列化 (無需額外宣告)。
class SuperC implements Serializable { int supervalue; }
class SubC extends SuperC { int subvalue; } // 自動可序列化
常見場景:
為讓非序列化父類的子類可序列化,子類必須:否則執行時拋出
- 父類具有 可存取的無參數建構子 (public/protected)
- 子類 自行負責 儲存/還原父類的
public/protected/package-private欄位InvalidClassException。
class SuperC { // 未實作 Serializable
int supervalue;
public SuperC() {} // 必須有無參建構子
public SuperC(int v) { supervalue = v; }
}
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(); // 手動還原父類欄位
// 注意:父類無參建構子已由反序列化機制自動調用
}
}
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 介面,必須實作:
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; |
Serializable → 子類零成本繼承序列化能力writeObject/readObject 手動處理父類欄位serialVersionUID理解繼承與序列化的交互規則,是處理舊系統遷移、分散式傳輸、快取儲存的關鍵技能。