Object Cloning & Serialization

บทนำ: ปัญหาการคัดลอก 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 และข้อจำกัดของแต่ละเทคนิค