序列化是將物件狀態轉換為位元組流的過程,反序列化則是將位元組流還原為物件。這機制廣泛應用於網路傳輸、RMI 遠端呼叫、物件持久化等場景。Java 透過 java.io.Serializable 介面提供標準化支援,但其背後涉及的序列化 ID、transient 關鍵字、自定義序列化策略等細節,往往是開發者容易踩坑的地方。本文將透過原理解析與 ArrayList 源碼實戰,徹底講透 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();
InvalidClassException。建議顯式宣告避免跨平台/版本問題。null/0/false)。Serializable,否則父類別欄位重置為預設值。ArrayList 底層以 transient Object[] elementData 儲存元素,按理說不應被序列化。但實測序列化後元素完好無損。關鍵在於 ArrayList 定義了 private void writeObject(ObjectOutputStream s) 與 private void readObject(ObjectInputStream s) 兩個特殊方法。
當被序列化類別定義這兩個方法(簽名固定、private),JVM 會透過反射調用它們,而非預設的 defaultWriteObject/defaultReadObject。這賦予開發者完全控制序列化過程的能力:決定寫入什麼、如何寫入、讀取時如何重建。
// 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();
}
}
elementData 容量常大於實際大小,若直接序列化整個陣列,會寫入大量 null 浪費空間。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 確實是空介面。檢查發生在 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 層面的強制約束,非介面方法實作。
Serializable,並顯式宣告 serialVersionUID。transient。ArrayList、單例、代理模式)時,實作 writeObject/readObject,內部調用 defaultWriteObject/defaultReadObject 處理標準欄位,自行處理特殊邏輯。Externalizable 介面完全掌控序列化,但需自行處理所有細節,複雜度較高。理解序列化底層機制,不僅能避開常見坑點,更能在設計分散式系統、快取框架、深度複製工具時遊刃有餘。ArrayList 的實作堪稱教科書級範例,展現了如何在框架約束下實現高效、正確的自定義序列化策略。