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

Java 序列化與反序列化深度解析:Serializable、Externalizable、serialVersionUID 與版本兼容

     編輯:Java 程式設計入門

核心概念

  • 序列化:將物件狀態轉換為位元組序列,用於持久化儲存(檔案/資料庫)或網路傳輸(RPC、分散式快取、Session 複製)。
  • 反序列化:從位元組序列重建物件,不呼叫建構子,直接根據類元資料分配記憶體並填充欄位。

JDK 核心 API

類/介面關鍵方法用途
ObjectOutputStreamwriteObject(Object)序列化物件寫入輸出流
ObjectInputStreamreadObject()從輸入流讀取並反序列化物件
Serializable標記介面 (無方法)啟用預設序列化機制
ExternalizablewriteExternal/readExternal完全自定義序列化邏輯

完整範例:預設序列化流程

import java.io.*;
import java.util.Date;

public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        // 序列化
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("objectFile.obj"))) {

            Customer customer = new Customer("阿蜜果", 24);
            out.writeObject("你好!");
            out.writeObject(new Date());
            out.writeObject(customer);
            out.writeInt(123);
        }

        // 反序列化 (順序必須一致)
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("objectFile.obj"))) {

            System.out.println("obj1=" + (String) in.readObject());
            System.out.println("obj2=" + (Date) in.readObject());
            Customer obj3 = (Customer) in.readObject();
            System.out.println("obj3=" + obj3);
            int obj4 = in.readInt();
            System.out.println("obj4=" + obj4);
        }
    }
}

class Customer implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式宣告
    private String name;
    private int age;
    public Customer(String name, int age) { this.name = name; this.age = age; }
    @Override public String toString() { return "name=" + name + ", age=" + age; }
}

Serializable 進階控制

1. transient 關鍵字

標記欄位 不參與預設序列化,反序列化時該欄位為預設值 (null/0/false)。適用於敏感資料 (密碼)、衍生欄位、非序列化第三方物件引用。

2. 自定義 writeObject / readObject

在類中定義 private 方法,JVM 會自動調用:

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

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

3. static 欄位不序列化

靜態屬於類不屬於實例,預設機制忽略。若需持久化需手動在 writeObject 中處理。

Externalizable:完全掌控

  • 實現 Externalizable 必須實作 writeExternal(ObjectOutput)、readExternal(ObjectInput)。
  • 反序列化時 會呼叫無參建構子 (Serializable 不會),因此類必須提供 public 無參建構子,否則拋 InvalidClassException。
  • 適用場景:極致效能優化、特殊格式輸出、需控制寫入順序/壓縮/加密。

serialVersionUID:版本兼容的關鍵

為什麼要顯式宣告?

  • 預設由 JVM 根據類結構自動生成,不同編譯器/版本可能產生不同值。
  • 反序列化時若 UID 不匹配 → InvalidClassException。

兩種策略

策略UID 設定適用場景
兼容升級固定同一值 (如 1L)新增欄位、方法、實現新介面;舊資料仍可反序列化,新欄位得預設值
強制不兼容修改 UID (如 2L)欄位刪除/型別變更/語義改變,舊資料不應被讀取
// 兼容升級範例
class CustomerV2 implements Serializable {
    private static final long serialVersionUID = 1L; // 保持不變
    private String name;
    private int age;
    private String email; // 新增欄位,舊資料反序列化時 email = null
    // ...
}

序列化效能優化建議

  1. 盡量標記 transient 減少傳輸體積 (大集合、快取、日誌物件等)。
  2. 大量物件考慮 Externalizable 或改用高效二進制協議 (Protobuf, Avro, Kryo, Hessian)。
  3. 避免序列化整個物件圖 (循環引用、深層嵌套) 導致遞歸遍歷爆棧/耗時。
  4. 頻繁網路傳輸場景:啟用壓縮 (GZIPOutputStream) 或使用 Netty/Kryo 等框架。

安全性警示

反序列化不可信資料極度危險 (CVE-2015-4852 等 Gadget Chain 攻擊)。生產環境務必:

  • 禁止直接反序列化用戶輸入的位元組流。
  • 使用 ObjectInputFilter (JDK 9+) 白名單限制可反序列化類別。
  • 採用安全協議 (JSON/Protobuf) 替代 Java 原生序列化進行跨系統通訊。

總結

Java 序列化雖然內建便利,但預設機制脆弱、效能一般、版本演進易出錯、安全風險高。現代架構建議:

  • 內部緩存/Session 複製:可用原生序列化 + 顯式 UID + transient。
  • 跨服務通訊/持久化:優先選 Protobuf / JSON / Avro,嚴格定義 Schema。
  • 若必須用原生序列化:實作 ObjectInputFilter、定期審計、最小權限原則。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved