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

Java 物件序列化完整指南:機制、實戰與進階定制

     編輯:Java 相關知識

什麼是物件序列化?

物件序列化是將物件狀態轉換為位元組流的過程;反序列化則是從位元組流重建物件。此機制讓物件能:

  • 持久化至檔案/資料庫
  • 透過網路傳輸(RMI、Socket、JMS、EJB 底層皆仰賴此)
  • 實現深複製(含引用物件圖)
  • 跨 JVM、跨平台交換資料(Windows 序列化 → Linux 反序列化)

核心 API 快速查閱

類別/介面角色關鍵方法
java.io.Serializable標記介面,無方法—
java.io.Externalizable完全自控序列化writeExternal, readExternal
ObjectOutputStream序列化輸出writeObject(Object), defaultWriteObject()
ObjectInputStream反序列化輸入readObject(), defaultReadObject()

基礎實戰:序列化與反序列化


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

// 1. 實作 Serializable(標記介面)
class Employee implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式宣告
    private String name;
    private double salary;
    private transient Date hireDate; // transient 不序列化
    
    public Employee(String name, double salary, Date hireDate) {
        this.name = name; this.salary = salary; this.hireDate = hireDate;
    }
    // getters/setters/toString 省略
}

public class SerializationBasics {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee emp = new Employee("Alice", 85000, new Date());
        
        // --- 序列化寫入檔案 ---
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("employee.ser"))) {
            out.writeObject(emp);
            System.out.println("序列化完成");
        }
        
        // --- 反序列化讀取 ---
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("employee.ser"))) {
            Employee restored = (Employee) in.readObject();
            System.out.println("反序列化結果: " + restored);
            // 注意:hireDate 為 null(transient 跳過)
        }
    }
}

進階主題一:自訂序列化邏輯

當物件包含非序列化欄位(如 Socket、Thread、第三方庫物件)或需加密/壓縮敏感欄位時,需實作 writeObject / readObject。


class ConnectionWrapper implements Serializable {
    private static final long serialVersionUID = 1L;
    private String host;
    private int port;
    private transient Socket socket; // 非序列化
    
    public ConnectionWrapper(String host, int port) {
        this.host = host; this.port = port;
        this.socket = new Socket(host, port);
    }
    
    // 序列化時:儲存重建連線所需資訊
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 先處理非 transient 欄位
        out.writeObject(host);
        out.writeInt(port);
    }
    
    // 反序列化時:重建連線
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        this.host = (String) in.readObject();
        this.port = in.readInt();
        this.socket = new Socket(host, port); // 重新連線
    }
}

關鍵規則:

  1. 方法必須為 private,由 JVM 自動調用。
  2. 務必呼叫 defaultWriteObject() / defaultReadObject() 處理標準欄位。
  3. 讀寫順序必須完全一致。

進階主題二:Externalizable 完全掌控

實作 Externalizable 介面,類別需自行處理所有欄位(含父類別),JVM 僅寫入類別身份資訊。


class FullControl implements Externalizable {
    private String data;
    private int version;
    
    public FullControl() {} // 必須有無參建構子
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(data);
        out.writeInt(version);
        // 可加入加密、壓縮、版本控制邏輯
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.data = (String) in.readObject();
        this.version = in.readInt();
        // 解密、解壓縮、版本遷移
    }
}

⚠ 安全警示: writeExternal/readExternal 為 public,惡意代碼可竄改序列化資料。傳輸敏感資料時務必加密或使用安全通道。

序列化機制深度解析

1. 物件引用去重

ObjectOutputStream 維護一份「已序列化物件表」。同一物件圖中多次引用同一實例時,僅第一次寫入完整物件,後續寫入參考標記,避免重複、解決循環引用。

2. serialVersionUID 的作用

  • 反序列化時,JVM 比對流中的 UID 與類別宣告的 UID。
  • 不一致拋出 InvalidClassException。
  • 最佳實踐: 顯式宣告 private static final long serialVersionUID = 1L;,避免編譯器自動生成導致跨版本不相容。

3. 繼承與序列化

  • 父類別實作 Serializable → 子類別自動可序列化。
  • 父類別未實作 → 反序列化時父類別欄位透過無參建構子初始化(需確保父類別有無參建構子)。

常見陷阱與避坑指南

陷阱現象解決方案
忘記宣告 serialVersionUID跨 JVM/版本反序列化失敗顯式宣告固定值
static/transient 欄位預期被序列化反序列化後為預設值理解修飾語語義,必要時自訂 writeObject
序列化非最終類別被惡意繼承安全風險敏感類別用 final 或 readObject 驗證
大量物件序列化導致 OOM記憶體飆升分批序列化、使用緩衝流、考慮替代格式
修改類別結構未更新 UIDInvalidClassException維護 UID 版本策略

現代替代方案

雖然 Java 原生序列化功能強大,但存在安全漏洞、效能較差、跨語言困難等問題。現代專案建議考慮:

  • JSON:Jackson、Gson(人類可讀、跨語言、生態豐富)
  • Protocol Buffers / Avro:高效能、強結構、Schema 演進支援
  • Kryo / FST:Java 專用高效能二進制序列化

總結

Java 序列化是分散式系統、持久化框架的基石。掌握 Serializable 基礎用法、transient 與自訂 writeObject/readObject 進階技巧、Externalizable 完全掌控模式,並理解 serialVersionUID、引用去重、繼承關係等底層機制,能讓你在處理物件傳輸、快取、深複製等場景時遊刃有餘。同時,務必評估安全風險,適時引入現代序列化方案替代。

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