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

Java 序列化 (Serializable) 深度解析:物件持久化與反序列化實戰

     編輯:Java 程式設計入門

序列化核心概念

Java 序列化將物件狀態轉為位元組序列,用於持久化儲存、網路傳輸、跨 JVM 通訊 (RMI、分散式快取)。實作 java.io.Serializable 標記介面即可啟用預設機制。

完整實戰範例:巢狀物件與集合序列化

1. 可序列化實體類別


import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class Student implements Serializable {
    // 1. 顯式宣告 serialVersionUID (強烈建議)
    private static final long serialVersionUID = 1L;

    private String id;
    private String name;
    private int age;
    // 巢狀集合:學生的學生 (遞迴結構)
    private List students = new ArrayList<>();

    // 建構子、Getter/Setter 省略...

    @Override
    public String toString() {
        return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
    }
}

2. 序列化/反序列化工具類


import java.io.*;

public class SerializationUtil {

    /**
     * 物件序列化到檔案
     */
    public static void serialize(Object obj, String filePath) throws IOException {
        File file = new File(filePath);
        // 確保父目錄存在
        file.getParentFile().mkdirs();

        try (FileOutputStream fos = new FileOutputStream(file);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(obj);
            oos.flush();
        }
    }

    /**
     * 從檔案反序列化物件
     */
    public static  T deserialize(String filePath, Class clazz) 
            throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream(filePath);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            Object obj = ois.readObject();
            return clazz.cast(obj);
        }
    }

    /**
     * 深度複製 (利用序列化副作用)
     */
    @SuppressWarnings("unchecked")
    public static  T deepClone(T obj) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos);
             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            oos.writeObject(obj);
            return (T) ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException("深度複製失敗", e);
        }
    }
}

3. 測試案例:複雜物件圖


public class SerializationDemo {
    public static void main(String[] args) {
        // 建立複雜物件圖
        Student root = new Student();
        root.setId("100");
        root.setName("張老師");
        root.setAge(50);

        Student s1 = new Student(); s1.setId("1"); s1.setName("張1"); s1.setAge(20);
        Student s2 = new Student(); s2.setId("2"); s2.setName("張2"); s2.setAge(21);
        Student s3 = new Student(); s3.setId("3"); s3.setName("張3"); s3.setAge(22);

        Student s4 = new Student(); s4.setId("4"); s4.setName("張4"); s4.setAge(23);
        Student s41 = new Student(); s41.setId("41"); s41.setName("張41"); s41.setAge(24);
        s4.getStudents().add(s41);

        root.getStudents().addAll(Arrays.asList(s1, s2, s3, s4));

        String path = "C:/temp/student.ser";

        // 序列化
        try {
            SerializationUtil.serialize(root, path);
            System.out.println("序列化完成,檔案大小: " + new File(path).length() + " bytes");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try {
            Student restored = SerializationUtil.deserialize(path, Student.class);
            printStudentTree(restored, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void printStudentTree(Student s, int level) {
        if (s == null) return;
        System.out.println("  ".repeat(level) + s);
        if (s.getStudents() != null) {
            for (Student sub : s.getStudents()) {
                printStudentTree(sub, level + 1);
            }
        }
    }
}

核心機制深度解析

serialVersionUID 的關鍵作用

  • 版本控制:反序列化時 JVM 比對 UID,不一致拋 InvalidClassException
  • 自動生成風險:未顯式宣告時由編譯器根據類結構生成,修改類會導致 UID 變化
  • 最佳實踐:private static final long serialVersionUID = 1L; 手動維護,兼容性修改時不變

transient 關鍵字:排除敏感/非必要欄位


public class User implements Serializable {
    private String username;
    private transient String password;      // 不序列化 (安全)
    private transient Logger logger = Logger.getLogger(...); // 非序列化資源
    private transient int cacheHash;        // 衍生欄位,反序列化後重算

    // 自定義反序列化邏輯重建 transient 欄位
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        this.logger = Logger.getLogger(getClass());
        this.cacheHash = computeHash();
    }
}

自定義序列化協議


private void writeObject(ObjectOutputStream oos) throws IOException {
    oos.defaultWriteObject(); // 先寫入非 transient 欄位
    oos.writeInt(students.size()); // 手寫集合大小
    for (Student s : students) {
        oos.writeObject(s); // 遞迴寫入
    }
}

private void readObject(ObjectInputStream ois) 
        throws IOException, ClassNotFoundException {
    ois.defaultReadObject();
    int size = ois.readInt();
    this.students = new ArrayList<>(size);
    for (int i = 0; i < size; i++) {
        students.add((Student) ois.readObject());
    }
}

進階主題與避坑指南

1. 單例模式序列化破壞問題


public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() { return INSTANCE; }

    // 關鍵:readResolve 確保反序列化返回同一實例
    protected Object readResolve() {
        return INSTANCE;
    }
}

2. 繼承與序列化

  • 父類實作 Serializable → 子類自動可序列化
  • 父類未實作 Serializable → 子類序列化時父類欄位不保存,反序列化會呼叫父類無參建構子初始化父類欄位

3. 效能與替代方案對比

方案優點缺點適用場景
Java 原生序列化零配置、支援任意物件圖體積大、速度慢、不跨語言、安全風險臨時快取、RMI、Session 複製
JSON (Jackson/Gson)可讀、跨語言、生態豐富需 POJO、循環引用需配置Web API、配置檔、日誌
Protobuf/Thrift極小體積、極快、Schema 演進需定義 .proto、學習曲線高效能 RPC、儲存引擎
Kryo/FST比原生快 5-10 倍、體積小第三方依賴分散式快取、Spark 任務

4. 安全風險與防範

  • 反序列化漏洞:惡意構造位元組流執行任意代碼 (Apache Commons Collections、Spring 等 Gadget 鏈)
  • 防護:JDK 9+ ObjectInputFilter、不信任來源禁用反序列化、使用 JSON 替代
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved