Java 序列化 (Serialization)

序列化是將物件轉換為位元組串流的過程,可用於儲存到檔案或透過網路傳輸。反序列化則是將位元組串流還原為物件。

實作 Serializable

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private transient String password;  // 不會被序列化

    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }

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

序列化物件

import java.io.*;

Person person = new Person("Alice", 30, "secret123");

// 序列化到檔案
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("person.dat"))) {
    oos.writeObject(person);
} catch (IOException e) {
    e.printStackTrace();
}

反序列化物件

// 從檔案反序列化
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("person.dat"))) {
    Person person = (Person) ois.readObject();
    System.out.println(person);
    // Person{name='Alice', age=30, password='null'}
    // password 是 transient,不會被序列化
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

serialVersionUID

public class User implements Serializable {
    // 建議明確定義,避免反序列化時版本不符
    private static final long serialVersionUID = 1L;

    private String username;
    private String email;
}

如果不定義 serialVersionUID,Java 會自動生成,但類別有任何變動都會導致反序列化失敗。

transient 關鍵字

public class Account implements Serializable {
    private String accountNumber;
    private double balance;
    private transient String pin;        // 敏感資料不序列化
    private transient Connection conn;   // 不可序列化的物件
}

自訂序列化

public class CustomObject implements Serializable {
    private String data;
    private transient String processedData;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();  // 預設序列化
        // 自訂額外處理
        oos.writeObject(encrypt(data));
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();  // 預設反序列化
        // 自訂額外處理
        this.processedData = process(data);
    }

    private String encrypt(String s) { return s; }  // 示例
    private String process(String s) { return s; }  // 示例
}

序列化到位元組陣列

// 序列化
public static byte[] serialize(Object obj) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(obj);
    }
    return baos.toByteArray();
}

// 反序列化
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
    try (ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(data))) {
        return ois.readObject();
    }
}

// 使用
Person person = new Person("Bob", 25, "pass");
byte[] bytes = serialize(person);
Person restored = (Person) deserialize(bytes);

深層複製

@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T obj) {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

現代替代方案

Java 原生序列化有安全性問題,現代開發建議使用:

JSON (使用 Jackson)

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

// 序列化
String json = mapper.writeValueAsString(person);
mapper.writeValue(new File("person.json"), person);

// 反序列化
Person person = mapper.readValue(json, Person.class);

Record 類型(Java 16+)

public record PersonRecord(String name, int age) implements Serializable {}

重點整理

  • 類別需實作 Serializable 介面
  • serialVersionUID 用於版本控制
  • transient 標記不需序列化的欄位
  • 可透過 writeObject/readObject 自訂序列化行為
  • Java 原生序列化有安全性問題,建議使用 JSON 等格式
  • 深層複製可透過序列化實現