Java 序列化將物件狀態轉為位元組序列,用於持久化儲存、網路傳輸、跨 JVM 通訊 (RMI、分散式快取)。實作 java.io.Serializable 標記介面即可啟用預設機制。
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 + "}";
}
}
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);
}
}
}
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);
}
}
}
}
InvalidClassExceptionprivate static final long serialVersionUID = 1L; 手動維護,兼容性修改時不變
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());
}
}
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;
}
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Java 原生序列化 | 零配置、支援任意物件圖 | 體積大、速度慢、不跨語言、安全風險 | 臨時快取、RMI、Session 複製 |
| JSON (Jackson/Gson) | 可讀、跨語言、生態豐富 | 需 POJO、循環引用需配置 | Web API、配置檔、日誌 |
| Protobuf/Thrift | 極小體積、極快、Schema 演進 | 需定義 .proto、學習曲線 | 高效能 RPC、儲存引擎 |
| Kryo/FST | 比原生快 5-10 倍、體積小 | 第三方依賴 | 分散式快取、Spark 任務 |
ObjectInputFilter、不信任來源禁用反序列化、使用 JSON 替代