บทนำ: ปัญหาการคัดลอก Objects
ในการเขียนโปรแกรม เรามักต้องการสำเนา (copy) ของ object แต่การคัดลอกมีความซับซ้อนมากกว่าที่คิด
ลองพิจารณาสถานการณ์ง่ายๆ เราต้องการสำเนา object:
javaPerson person1 = new Person("Alice", 25);
Person person2 = person1; // ← คัดลอกหรือไม่?
ปัญหาคือ การ assign แบบนี้ ไม่ได้คัดลอก object แต่คัดลอกเพียง reference (ตัวชี้) เท่านั้น ส่งผลให้ person1 และ person2 ชี้ไปที่ object เดียวกัน:
javaperson2.setAge(30);
System.out.println(person1.getAge()); // 30 ← ไม่ใช่ 25!
เมื่อเปลี่ยน person2 ค่า person1 เปลี่ยนตาม เพราะมันคือ object เดียวกัน
สิ่งนี้เป็นปัญหา เมื่อ:
- ต้องการ backup ของ object ก่อนทำการแก้ไข
- ต้องการ snapshot ของ state ณ เวลาหนึ่ง
- ต้องการ pass object แต่ไม่ต้องการให้ถูกแก้ไข
- ต้องการสร้าง prototype (ต้นแบบ) แล้ว clone เป็นหลายตัว
Object Cloning คือวิธีสร้างสำเนา object ที่เป็นอิสระจากต้นฉบับ
Serialization คือวิธีอีกทางที่ไม่เพียงแค่สำเนา แต่ยังเก็บ object ลงไฟล์หรือส่งผ่านเครือข่ายได้
Object Cloning: สร้างสำเนา
Shallow Copy vs Deep Copy
ก่อนที่จะเข้าใจ cloning ต้องเข้าใจความแตกต่างระหว่าง shallow copy และ deep copy:
Shallow Copy คัดลอกเฉพาะ field ของ object แต่ถ้า field นั้นเป็น reference ไปยัง object อื่น มันจะคัดลอก reference เท่านั้น ไม่ใช่ object ที่ชี้ไป
Deep Copy คัดลอกทั้ง object และ objects ที่มันอ้างถึง สร้างสำเนาที่สมบูรณ์และอิสระจากต้นฉบับ
มาดูตัวอย่างเพื่อทำความเข้าใจ:
javapublic class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
@Override
public String toString() {
return city + ", " + street;
}
}
public class Person {
private String name;
private int age;
private Address address; // ← Reference ไป object อื่น
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() { return name; }
public int getAge() { return age; }
public Address getAddress() { return address; }
public void setAge(int age) { this.age = age; }
}
ในตัวอย่างนี้ Person มี field address ที่เป็น reference ไปยัง Address object
ตัวอย่างที่ 1: Implementing Cloneable
วิธี Clone ด้วย Cloneable Interface
Java มี interface ชื่อ Cloneable ที่ใช้เป็น marker (เครื่องหมาย) ว่า class นี้สามารถ clone ได้:
javapublic class Person implements Cloneable {
private String name;
private int age;
private Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// Override clone() method
@Override
public Person clone() {
try {
// ← clone() method จาก Object class
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone failed", e);
}
}
public String getName() { return name; }
public int getAge() { return age; }
public Address getAddress() { return address; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Person{" + "name='" + name + "', age=" + age +
", address=" + address + "}";
}
}
// ใช้งาน
public class CloningDemo {
public static void main(String[] args) {
Address address = new Address("Bangkok", "Sukhumvit Rd.");
Person person1 = new Person("Alice", 25, address);
// Clone person
Person person2 = person1.clone();
System.out.println("Before modification:");
System.out.println("Person1: " + person1);
System.out.println("Person2: " + person2);
// แก้ไข person2
person2.setAge(30);
System.out.println("\nAfter modifying age:");
System.out.println("Person1: " + person1); // age ยังคง 25
System.out.println("Person2: " + person2); // age เป็น 30
}
}
// OUTPUT:
// Before modification:
// Person1: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
// Person2: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
//
// After modifying age:
// Person1: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
// Person2: Person{name='Alice', age=30, address=Bangkok, Sukhumvit Rd.}
ดูเหมือนว่าทำงานได้ดี! แต่มีปัญหาซ่อนอยู่ วิธีนี้เป็น shallow copy
ปัญหา Shallow Copy
ทำไม Shallow Copy ไม่เพียงพอ
ลองทดสอบเพิ่มเติม ถ้าแก้ไข address:
javapublic class ShallowCopyProblem {
public static void main(String[] args) {
Address address = new Address("Bangkok", "Sukhumvit Rd.");
Person person1 = new Person("Alice", 25, address);
// Clone
Person person2 = person1.clone();
System.out.println("Before modifying address:");
System.out.println("Person1: " + person1);
System.out.println("Person2: " + person2);
// แก้ไข address ของ person2
person2.getAddress().setCity("Chiang Mai"); // ← ปัญหา!
System.out.println("\nAfter modifying address:");
System.out.println("Person1: " + person1); // ← city เปลี่ยนด้วย!
System.out.println("Person2: " + person2);
}
}
// OUTPUT:
// Before modifying address:
// Person1: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
// Person2: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
//
// After modifying address:
// Person1: Person{name='Alice', age=25, address=Chiang Mai, Sukhumvit Rd.} ← เปลี่ยนด้วย!
// Person2: Person{name='Alice', age=30, address=Chiang Mai, Sukhumvit Rd.}
ปัญหาเกิดขึ้นเพราะ shallow copy คัดลอก reference ของ address ไม่ใช่ Address object จริง ดังนั้น person1 และ person2 ยังคงใช้ address object เดียวกัน
ตัวอย่างที่ 2: Deep Copy
วิธีสร้าง Deep Copy
เพื่อแก้ปัญหานี้ ต้องสร้าง deep copy โดย clone ทุก object ที่ถูกอ้างถึง:
javapublic class Address implements Cloneable {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// Address ต้อง cloneable ด้วย
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone failed", e);
}
}
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
@Override
public String toString() {
return city + ", " + street;
}
}
public class Person implements Cloneable {
private String name;
private int age;
private Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Person clone() {
try {
Person cloned = (Person) super.clone();
// ← Deep copy: clone address ด้วย
cloned.address = this.address.clone();
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone failed", e);
}
}
public String getName() { return name; }
public int getAge() { return age; }
public Address getAddress() { return address; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Person{" + "name='" + name + "', age=" + age +
", address=" + address + "}";
}
}
// ทดสอบ deep copy
public class DeepCopyDemo {
public static void main(String[] args) {
Address address = new Address("Bangkok", "Sukhumvit Rd.");
Person person1 = new Person("Alice", 25, address);
// Clone with deep copy
Person person2 = person1.clone();
System.out.println("Before modifying address:");
System.out.println("Person1: " + person1);
System.out.println("Person2: " + person2);
// แก้ไข address ของ person2
person2.getAddress().setCity("Chiang Mai");
System.out.println("\nAfter modifying address:");
System.out.println("Person1: " + person1); // ← ไม่เปลี่ยน!
System.out.println("Person2: " + person2);
}
}
// OUTPUT:
// Before modifying address:
// Person1: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
// Person2: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.}
//
// After modifying address:
// Person1: Person{name='Alice', age=25, address=Bangkok, Sukhumvit Rd.} ← ไม่เปลี่ยน!
// Person2: Person{name='Alice', age=30, address=Chiang Mai, Sukhumvit Rd.}
ตอนนี้ person1 และ person2 เป็นอิสระจากกันอย่างสมบูรณ์ เพราะแม้แต่ address object ก็ถูก clone แยกออกมา
Serialization: บันทึกและโหลด Objects
แนวคิด Serialization
Serialization คือกระบวนการแปลง object เป็น byte stream เพื่อ:
- เก็บลงไฟล์ – บันทึก state ของ object
- ส่งผ่านเครือข่าย – ส่ง object ไปยังเครื่องอื่น
- Deep cloning – วิธีอื่นในการสร้าง deep copy
Deserialization คือกระบวนการตรงกันข้าม แปลง byte stream กลับเป็น object
Java มี interface Serializable ที่เป็น marker ว่า class นี้สามารถ serialize ได้:
javaimport java.io.*;
public class Student implements Serializable {
// serialVersionUID ใช้ track version ของ class
private static final long serialVersionUID = 1L;
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
public String getName() { return name; }
public int getAge() { return age; }
public double getGpa() { return gpa; }
@Override
public String toString() {
return "Student{" + "name='" + name + "', age=" + age +
", gpa=" + gpa + "}";
}
}
// การ serialize และ deserialize
public class SerializationDemo {
// บันทึก object ลงไฟล์
public static void saveStudent(Student student, String filename) {
try (ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(student);
System.out.println("Student saved to " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
// โหลด object จากไฟล์
public static Student loadStudent(String filename) {
try (ObjectInputStream in =
new ObjectInputStream(new FileInputStream(filename))) {
Student student = (Student) in.readObject();
System.out.println("Student loaded from " + filename);
return student;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
// สร้าง student
Student student1 = new Student("Alice", 20, 3.75);
System.out.println("Original: " + student1);
// บันทึกลงไฟล์
saveStudent(student1, "student.ser");
// โหลดกลับมา
Student student2 = loadStudent("student.ser");
System.out.println("Loaded: " + student2);
// ตรวจสอบว่าเป็น object คนละตัว
System.out.println("Same object? " + (student1 == student2)); // false
System.out.println("Equal data? " + student1.toString().equals(student2.toString())); // true
}
}
// OUTPUT:
// Original: Student{name='Alice', age=20, gpa=3.75}
// Student saved to student.ser
// Student loaded from student.ser
// Loaded: Student{name='Alice', age=20, gpa=3.75}
// Same object? false
// Equal data? true
ข้อดี:
- Object ที่โหลดกลับมา เป็น object ใหม่ที่อิสระจากต้นฉบับ
- ไม่ต้องเขียน code สำหรับ clone แต่ละ field
- สามารถเก็บ complex object graphs ได้
ตัวอย่างที่ 3: Deep Copy ด้วย Serialization
Serialization เป็น Cloning Technique
วิธีฉลาดในการทำ deep copy คือใช้ serialization:
javaimport java.io.*;
public class SerializationCloner {
// Deep clone โดยใช้ serialization
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepClone(T object) {
try {
// เขียน object ลง byte array
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(object);
out.close();
// อ่าน object กลับจาก byte array
ByteArrayInputStream byteIn =
new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
T cloned = (T) in.readObject();
in.close();
return cloned;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Clone failed", e);
}
}
}
// ทดสอบ
public class SerializationCloneDemo {
public static void main(String[] args) {
Address address = new Address("Bangkok", "Sukhumvit Rd.");
Person person1 = new Person("Alice", 25, address);
// Deep clone ด้วย serialization
Person person2 = SerializationCloner.deepClone(person1);
System.out.println("Before modification:");
System.out.println("Person1: " + person1);
System.out.println("Person2: " + person2);
// แก้ไข address
person2.getAddress().setCity("Chiang Mai");
System.out.println("\nAfter modification:");
System.out.println("Person1: " + person1); // ← ไม่เปลี่ยน
System.out.println("Person2: " + person2);
}
}
วิธีนี้สะดวกมาก เพราะไม่ต้อง implement clone() method และ handle nested objects เอง serialization ทำให้อัตโนมัติ
transient Keyword: ข้อมูลที่ไม่ Serialize
ป้องกัน Field ไม่ให้ Serialize
บางครั้ง field บาง field ไม่ควร serialize เช่น:
- Password หรือข้อมูลลับ
- Temporary data ที่คำนวณได้ใหม่
- Connection objects ที่ไม่สามารถ serialize ได้
ใช้ keyword transient เพื่อทำเครื่องหมาย:
javaimport java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // ← ไม่ serialize
private int loginCount;
public User(String username, String password, int loginCount) {
this.username = username;
this.password = password;
this.loginCount = loginCount;
}
@Override
public String toString() {
return "User{" + "username='" + username + "', password='" +
password + "', loginCount=" + loginCount + "}";
}
}
// ทดสอบ
public class TransientDemo {
public static void main(String[] args) throws Exception {
User user1 = new User("alice", "secret123", 5);
System.out.println("Before serialization: " + user1);
// Serialize
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(user1);
out.close();
// Deserialize
ByteArrayInputStream byteIn =
new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
User user2 = (User) in.readObject();
in.close();
System.out.println("After deserialization: " + user2);
}
}
// OUTPUT:
// Before serialization: User{username='alice', password='secret123', loginCount=5}
// After deserialization: User{username='alice', password='null', loginCount=5}
// ↑ เป็น null!
Field password ที่มี transient ไม่ถูก serialize ดังนั้นเมื่อ deserialize กลับมา มันกลายเป็น null
Best Practices & Cautions
ข้อควรระวังและแนวทางปฏิบัติ
Object Cloning:
ข้อดี:
- สามารถสร้างสำเนาที่อิสระจากต้นฉบับ
- มีประโยชน์สำหรับ undo/redo, prototype pattern
- Shallow copy เร็วกว่า deep copy
ข้อควรระวัง:
- ต้องแน่ใจว่าเป็น shallow หรือ deep copy
- Cloneable มีปัญหาหลายอย่าง (ไม่มี type safety, checked exception)
- Alternative: copy constructor หรือ factory method
Serialization:
ข้อดี:
- เก็บ object ลงไฟล์หรือส่งผ่านเครือข่าย
- สามารถใช้ทำ deep cloning ได้
- Java handle complex object graphs อัตโนมัติ
ข้อควรระวัง:
- Performance – serialization ช้า
- Security – deserialize data จาก untrusted source อันตราย
- Compatibility – ต้อง maintain serialVersionUID
- ไม่ใช่ทุก class serialize ได้ (เช่น Thread, Socket)
สรุป
Object Cloning & Serialization เป็นเทคนิคสำคัญในการจัดการ objects:
Object Cloning ให้ความสามารถ:
- Shallow copy – คัดลอก object แต่ไม่คัดลอก nested objects
- Deep copy – คัดลอกทั้ง object และทุกสิ่งที่มันอ้างถึง
- สร้างสำเนาที่อิสระสำหรับ backup, undo/redo
Serialization ให้ความสามารถ:
- แปลง object เป็น byte stream
- เก็บ object ลงไฟล์
- ส่ง object ผ่านเครือข่าย
- ใช้เป็นวิธีทำ deep cloning
ตัวอย่างการใช้จริง:
- Version control systems – เก็บ snapshots ของ code
- Gaming – save/load game state
- Distributed systems – ส่ง objects ระหว่าง servers
- Caching – เก็บ objects สำหรับ performance
Object cloning และ serialization เป็นเครื่องมือที่มีพลัง แต่ต้องใช้อย่างระมัดระวัง ต้องเข้าใจ shallow vs deep copy และข้อจำกัดของแต่ละเทคนิค
