Object Graph ใน Java Serialization
Object Graph คือโครงสร้างของออบเจกต์ที่เชื่อมโยงกันผ่าน Reference เมื่อทำ Serialization ออบเจกต์ราก (Root Object) ทั้งกราฟที่เชื่อมต่อกันจะถูก Serialize อัตโนมัติ (Transitive Serialization)
หลักการทำงาน
- JVM ติดตาม Object References ทั้งหมดจาก Root Object
- ทุกออบเจกต์ในกราฟต้อง implement
Serializable - ใช้ Object Identity (==) เพื่อไม่ให้ Serialize ซ้ำ (Handle sharing)
- Circular references รองรับได้ (A→B→A)
ตัวอย่างโค้ดสมบูรณ์
import java.io.*;
import java.io.Serializable;
// Level 1
class One implements Serializable {
private static final long serialVersionUID = 1L;
Two two = new Two(); // Reference to Level 2
}
// Level 2
class Two implements Serializable {
private static final long serialVersionUID = 1L;
Three three = new Three(); // Reference to Level 3
}
// Level 3 (Leaf)
class Three implements Serializable {
private static final long serialVersionUID = 1L;
int i = 34;
int j = 67;
}
public class DemoSerialize {
public static void main(String[] args) throws Exception {
// สร้าง Object Graph
One root = new One();
// Serialize
try (FileOutputStream fos = new FileOutputStream("object_graph.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(root);
System.out.println("Serialized successfully");
}
// Deserialize
try (FileInputStream fis = new FileInputStream("object_graph.ser");
ObjectInputStream ois = new ObjectInputStream(fis)) {
One restored = (One) ois.readObject();
// เข้าถึงข้อมูลลึกในกราฟ
System.out.println("ค่า i หลัง Deserialize: " + restored.two.three.i);
System.out.println("ค่า j หลัง Deserialize: " + restored.two.three.j);
// ตรวจสอบว่าเป็น Object ใหม่ (ไม่ใช่ Same Reference)
System.out.println("Same object? " + (root == restored)); // false
System.out.println("Same nested? " + (root.two == restored.two)); // false
}
}
}
ผลลัพธ์
Serialized successfully
ค่า i หลัง Deserialize: 34
ค่า j หลัง Deserialize: 67
Same object? false
Same nested? false
การควบคุม Serialization
1. transient - ไม่ Serialize ฟิลด์นี้
class User implements Serializable {
String username;
transient String password; // ไม่ serialize (Security)
transient Thread thread; // Non-serializable object
}
2. custom writeObject/readObject
class CustomSerialization implements Serializable {
private transient String cache; // ไม่ serialize
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// เขียน cache อย่างอื่น (เช่น encrypt)
out.writeObject(encrypt(cache));
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// อ่านและ decrypt
this.cache = decrypt((String) in.readObject());
}
}
3. Externalizable Interface (ควบคุมเต็มที่)
class MyData implements Externalizable {
int id;
String data;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeUTF(data);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.data = in.readUTF();
}
}
serialVersionUID
- Version control สำหรับ Serialization
- ถ้าไม่ระบุ JVM คำนวณจากโครงสร้างคลาส (อาจเปลี่ยนเมื่อ compile ใหม่)
- แนะนำ: ประกาศ
private static final long serialVersionUID = 1L;เอง - เปลี่ยน UID เมื่อโครงสร้างเปลี่ยน incompatible (ลบ field, เปลี่ยน type)
ข้อควรระวัง
- Security: Deserialization ไม่เชื่อถือ → ใช้
ObjectInputFilter(Java 9+) - Performance: Java Serialization ช้า ใหญ่ ใช้ JSON (Jackson/Gson) หรือ Protobuf แทน
- Versioning: การเปลี่ยนแปลงคลาสต้องระวัง (เพิ่ม field OK, ลบ field ต้องมี default)
- Circular refs: รองรับได้ แต่อาจ StackOverflow ถ้าลึกมาก
ทางเลือกทันสมัย
- JSON: Jackson, Gson - Human readable, Cross-language
- Protocol Buffers: Google Protobuf - Binary, Schema evolution, Fast
- MessagePack: Binary JSON-like
- Java Records (Java 16+): Serializable โดยอัตโนมัติ เหมาะกับ Data Carrier