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

Java 物件序列化與反序列化深度實踐:從 JDK 原生到版本相容策略

     編輯:Java 相關知識

核心概念:為什麼需要序列化?

當兩個進程進行遠程通訊,或需將物件持久化至磁碟時,必須將物件轉為位元組序列。序列化 = 物件 → 位元組流;反序列化 = 位元組流 → 物件。兩大用途:

  1. 永久儲存物件狀態至檔案
  2. 網路傳輸物件 (RMI、分散式系統、Session 複製)

JDK 原生 API 快速上手

關鍵類別

  • ObjectOutputStream:writeObject(Object) 序列化寫入目標流
  • ObjectInputStream:readObject() 從來源流反序列化還原物件

基礎範例

// 序列化
ObjectOutputStream out = new ObjectOutputStream(
    new FileOutputStream("objectFile.obj"));
Customer c = new Customer("阿蜜果", 24);
out.writeObject("你好!");
out.writeObject(new Date());
out.writeObject(c);
out.writeInt(123);  // 基本型別也支援
out.close();

// 反序列化 (順序必須一致!)
ObjectInputStream in = new ObjectInputStream(
    new FileInputStream("objectFile.obj"));
System.out.println("obj1=" + (String) in.readObject());
System.out.println("obj2=" + (Date) in.readObject());
Customer c2 = (Customer) in.readObject();
System.out.println("obj3=" + c2);
System.out.println("obj4=" + in.readInt());
in.close();

輸出

obj1=你好!
obj2=Sat Sep 15 22:02:21 CST 2007
obj3=name=阿蜜果, age=24
obj4=123

實作 Serializable:預設機制與自訂控制

預設行為

  • 僅序列化 非 transient 的實例欄位
  • 不序列化:transient 欄位、static 欄位
  • 反序列化時不呼叫建構子,直接分配記憶體並填充欄位
  • 若類未載入,先載入類 (需 classpath 含類檔,否則 ClassNotFoundException)

自訂序列化邏輯

在類中私有定義:

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();  // 先執行預設序列化
    out.writeObject(encrypt(sensitiveData)); // 再寫入加密後的敏感資料
}

private void readObject(ObjectInputStream in) 
    throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    this.sensitiveData = decrypt((String) in.readObject());
}

效能優化建議

  • 大型物件圖遞迴遍歷耗時,將非必要欄位標記 transient
  • 頻繁序列化的類考慮實作 Externalizable 完全自控

實作 Externalizable:完全掌控權

介面繼承 Serializable,強制實作兩方法:

public void writeExternal(ObjectOutput out) throws IOException {
    out.writeUTF(name);
    out.writeInt(age);
    // 完全由開發者決定寫什麼、怎麼寫
}

public void readExternal(ObjectInput in) 
    throws IOException, ClassNotFoundException {
    this.name = in.readUTF();
    this.age = in.readInt();
}

關鍵差異:反序列化時會呼叫無參建構子,故類必須提供 public 無參建構子,否則拋 InvalidClassException。

版本相容性:serialVersionUID 的藝術

每個可序列化類都有 private static final long serialVersionUID。JVM 預設根據類結構自動生成,修改類後重編譯可能導致 UID 變更,造成反序列化失敗 (InvalidClassException)。

強烈建議:顯式宣告 UID

public class Customer implements Serializable {
    private static final long serialVersionUID = 1L; // 手動指定
    // ...
}

兩種策略

策略適用場景做法
相容新舊版本資料互通維持相同 UID;新增欄位預設值、舊欄位保留
不相容結構大幅變更、安全考量修改 UID (如 2L);強制舊資料無法載入

最佳實踐清單

  1. 務必顯式定義 serialVersionUID,避免跨 JVM/編譯器差異
  2. 敏感資料加密序列化,反序列化解密
  3. 大量非必要欄位標記 transient,提升效能
  4. 跨系統傳輸優先考慮 JSON/Protobuf,Java 原生序列化僅限 JVM 內部
  5. 測試反序列化相容性:單元測試覆蓋新舊版本互讀

結語

Java 原生序列化雖方便,但不適用於異構系統整合、長期持久化、高效能場景。理解其機制、掌握版本控制、善用 transient/Externalizable,才能在必須使用時(如舊系統維護、RMI、Session 複製)游刃有餘。新專案建議採用 Jackson、Gson、Protobuf、Avro 等跨語言序列化方案。

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