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

深度解析 Java 序列化與反序列化:從原理到 ArrayList 實戰

     編輯:Java 綜合教學

深度解析 Java 序列化與反序列化:從原理到 ArrayList 實戰

序列化是將物件狀態轉換為位元組流的過程,反序列化則是將位元組流還原為物件。這機制廣泛應用於網路傳輸、RMI 遠端呼叫、物件持久化等場景。Java 透過 java.io.Serializable 介面提供標準化支援,但其背後涉及的序列化 ID、transient 關鍵字、自定義序列化策略等細節,往往是開發者容易踩坑的地方。本文將透過原理解析與 ArrayList 源碼實戰,徹底講透 Java 序列化核心機制。

什麼是 Java 物件序列化?

JVM 運行時建立的物件生命週期隨 JVM 結束而終結。序列化讓物件「跨越」JVM 生命週期:將物件狀態(成員變數值)編碼為位元組序列,寫入檔案或網路流;日後讀取位元組流,重建物件狀態。需注意:序列化只保存物件的「狀態」(非 static、非 transient 成員變數),不包含方法、類定義、static 變數。

基本序列化與反序列化流程

實作 Serializable 介面(標記介面,無方法),配合 ObjectOutputStream.writeObject() 與 ObjectInputStream.readObject() 即可完成基本操作。範例中 User 類別包含 transient gender 與 static serialVersionUID,序列化後 gender 變為 null,驗證了 transient 與 static 不參與序列化。

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tempFile"));
User newUser = (User) ois.readObject();

核心知識點總覽

  • serialVersionUID:序列化版本號,反序列化時比對,不一致拋 InvalidClassException。建議顯式宣告避免跨平台/版本問題。
  • static 變數:屬於類不屬於實例,不序列化。
  • transient 關鍵字:阻止該欄位序列化,反序列化時恢復預設值(null/0/false)。
  • 父類別序列化:父類別也需實作 Serializable,否則父類別欄位重置為預設值。
  • 敏感資料加密:可在自定義序列化中對密碼等敏感欄位加密,反序列化時解密,提升安全性。

ArrayList 的序列化奧秘:為何 transient elementData 仍能保存元素?

ArrayList 底層以 transient Object[] elementData 儲存元素,按理說不應被序列化。但實測序列化後元素完好無損。關鍵在於 ArrayList 定義了 private void writeObject(ObjectOutputStream s) 與 private void readObject(ObjectInputStream s) 兩個特殊方法。

自定義序列化策略:writeObject / readObject

當被序列化類別定義這兩個方法(簽名固定、private),JVM 會透過反射調用它們,而非預設的 defaultWriteObject/defaultReadObject。這賦予開發者完全控制序列化過程的能力:決定寫入什麼、如何寫入、讀取時如何重建。

ArrayList 實作解析

// writeObject:只寫入實際元素,忽略陣列尾部空洞
private void writeObject(ObjectOutputStream s) throws IOException {
    int expectedModCount = modCount;
    s.defaultWriteObject(); // 寫入 non-transient 欄位
    s.writeInt(size);       // 寫入元素數量
    for (int i = 0; i < size; i++) s.writeObject(elementData[i]); // 逐個寫入元素
    if (modCount != expectedModCount) throw new ConcurrentModificationException();
}

// readObject:按寫入順序讀回元素,重建陣列
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
    s.defaultReadObject(); // 讀取 non-transient 欄位
    s.readInt();           // 讀取 capacity(忽略)
    if (size > 0) {
        ensureCapacityInternal(size);
        Object[] a = elementData;
        for (int i = 0; i < size; i++) a[i] = s.readObject();
    }
}

設計動機:空間與效率的平衡

  • 為何 transient? elementData 容量常大於實際大小,若直接序列化整個陣列,會寫入大量 null 浪費空間。
  • 為何自定義? 必須保證元素不丟失。遍歷寫入實際元素,反序列化時按順序重建緊湊陣列,兼顧空間效率與資料完整性。

writeObject/readObject 如何被調用?反射機制揭秘

ObjectOutputStream 呼叫鏈:writeObject → writeObject0 → writeOrdinaryObject → writeSerialData → invokeWriteObject。關鍵在 invokeWriteObject 中:

void invokeWriteObject(Object obj, ObjectOutputStream out) {
    if (writeObjectMethod != null) {
        writeObjectMethod.invoke(obj, new Object[]{ out }); // 反射調用類別自定義的 writeObject
    }
}

writeObjectMethod 即類別定義的 writeObject 方法。反序列化同理透過 ObjectInputStream 反射調用 readObject。這就是「約定優於配置」的體現:方法簽名固定、私有、拋特定異常,JVM 自動發現並調用。

Serializable 空介面如何強制檢查?

Serializable 確實是空介面。檢查發生在 ObjectOutputStream.writeObject0 中:

if (obj instanceof String) writeString(...);
else if (cl.isArray()) writeArray(...);
else if (obj instanceof Enum) writeEnum(...);
else if (obj instanceof Serializable) writeOrdinaryObject(...); // 關鍵判斷
else throw new NotSerializableException(...);

只有 String、Enum、Array、實作 Serializable 的物件才能進入序列化流程,否則直接拋出異常。這是 JVM 層面的強制約束,非介面方法實作。

總結與最佳實踐

  1. 需序列化的類別必須實作 Serializable,並顯式宣告 serialVersionUID。
  2. 非必要欄位(敏感資料、衍生資料、非序列化物件引用)加 transient。
  3. 需精確控制序列化格式(如 ArrayList、單例、代理模式)時,實作 writeObject/readObject,內部調用 defaultWriteObject/defaultReadObject 處理標準欄位,自行處理特殊邏輯。
  4. 考慮使用 Externalizable 介面完全掌控序列化,但需自行處理所有細節,複雜度較高。
  5. 跨語言/長期儲存建議採用 JSON、Protobuf、Avro 等與語言無關的序列化協議,Java 原生序列化存在版本鎖定、安全風險、效能不佳等缺點。

理解序列化底層機制,不僅能避開常見坑點,更能在設計分散式系統、快取框架、深度複製工具時遊刃有餘。ArrayList 的實作堪稱教科書級範例,展現了如何在框架約束下實現高效、正確的自定義序列化策略。

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