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

Java 物件序列化基礎入門:核心機制與實戰範例

     編輯:Java 相關知識

前言

物件序列化是 Java 分散式開發、持久化框架、快取中介軟體的基石。本文從零開始,系統講解序列化/反序列化核心流程、關鍵 API、自訂序列化策略、Externalizable 完全掌控模式,並附完整可執行範例。

核心概念速覽

  • 序列化:物件圖 → 位元組流(含類別描述、版本、資料)
  • 反序列化:位元組流 → 物件圖(JVM 依類別描述重建實例、填充欄位)
  • 深複製:序列化後反序列化,獲得物件圖完整獨立副本
  • 分散式物件:RMI、EJB 遠端呼叫底層即序列化傳輸參數/返回值

啟用序列化:實作 Serializable


import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式宣告
    private String username;
    private transient String password; // 敏感欄位不序列化
    private Date lastLogin;
    
    // 建構子、getter/setter、toString 省略
}

Serializable 為標記介面,無任何方法。JVM 見此介面即啟用預設序列化邏輯。

核心雙雄:ObjectOutputStream / ObjectInputStream

序列化寫入範例


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

public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        User user = new User("alice", "secret123", new Date());
        
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {
            out.writeObject("UserData");      // 寫入標識字串
            out.writeObject(user);            // 核心:遞歸序列化物件圖
            out.flush();
            System.out.println("序列化完成");
        }
    }
}

反序列化讀取範例


public class DeserializeDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("user.ser"))) {
            String tag = (String) in.readObject(); // 須按寫入順序讀取
            User user = (User) in.readObject();
            System.out.println("Tag: " + tag);
            System.out.println("User: " + user);
            // password 為 null(transient 跳過)
        }
    }
}

預設序列化的限制與自訂策略

1. transient 跳過欄位

  • 修飾欄位 → 預設序列化忽略,反序列化後為預設值(null/0/false)。
  • 適用:敏感資料、非序列化第三方物件、衍生/可重算欄位。

2. 自訂 writeObject / readObject

需私有、特定簽名,JVM 自動回呼。


class SecureUser implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String encryptedPassword; // 實際儲存加密後密文
    private transient String password;          // 明文不持久化
    
    // 序列化前:加密並寫入
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 處理非 transient 欄位
        // 簡單示範:Base64 編碼模擬加密
        String enc = java.util.Base64.getEncoder().encodeToString(password.getBytes());
        out.writeObject(enc);
    }
    
    // 反序列化後:解密還原
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        String enc = (String) in.readObject();
        this.encryptedPassword = enc;
        this.password = new String(java.util.Base64.getDecoder().decode(enc));
    }
}

鐵律: 必須呼叫 defaultWriteObject()/defaultReadObject();讀寫順序完全一致。

Externalizable:完全自控模式

實作 Externalizable,JVM 僅寫入類別身份,所有欄位由開發者手寫。


import java.io.Externalizable;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.IOException;

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

⚠ 安全提醒: 方法為 public,不可信代碼可竄改流資料。傳輸敏感資訊務必加密簽名。

序列化機制底層細節

  1. 物件引用去重:ObjectOutputStream 維護 handle 表,同一物件僅序列化一次,後續寫入 handle 參考。
  2. 類別描述寫入:首次遇到類別時寫入 ObjectStreamClass(含類名、serialVersionUID、欄位描述符)。
  3. 反序列化重建:JVM 讀取類別描述 → 載入類別 → 分配實例(不呼叫建構子) → 填充欄位 → 呼叫 readObject(若有)。
  4. serialVersionUID:版本標識,不匹配拋 InvalidClassException。務必顯式宣告固定值。

最佳實踐清單

  1. 所有可序列化類別顯式宣告 serialVersionUID。
  2. 敏感欄位(密碼、Token、金鑰)標記 transient,必要時自訂加密序列化。
  3. 避免序列化非最終類別(可被惡意繼承竄改),或於 readObject 中驗證類別完整性。
  4. 大量物件序列化考慮分批、緩衝、壓縮,或改用 Jackson/Protobuf/Kryo。
  5. 跨版本演進:新增欄位預設兼容;刪除欄位需保留 readObject 相容邏輯;修改欄位型別極高風險,建議新增欄位並棄用舊欄位。
  6. 測試:單元測試覆蓋序列化/反序列化往返、版本升級相容性、邊界條件(null、空集合、循環引用)。

總結

Java 原生序列化雖然強大,但需謹慎使用。掌握 Serializable 預設流程、transient 跳過機制、自訂 writeObject/readObject 進階控制、Externalizable 完全自控模式,並理解 handle 表、類別描述、serialVersionUID 等底層機制,是處理物件持久化、分散式通訊、深複製等場景的核心技能。現代架構中,建議優先評估 JSON、Protocol Buffers、Avro 等跨語言、高效能、安全的替代方案。

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