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 等格式
- 深層複製可透過序列化實現