Tìm hiểu về Deep Copy và Shallow Copy trong Java

Deep Copy và Shallow Copy trong Java

Trong lập trình hướng đối tượng, việc sao chép đối tượng là một thao tác phổ biến. Bài viết này sẽ phân tích chi tiết về hai khái niệm quan trọng: deep copy (sao chép sâu) và shallow copy (sao chép nông), cùng với các tình huống sử dụng thực tế.

1. Định nghĩa cơ bản

Shallow Copy (Sao chép nông)

Shallow copy tạo ra một đối tượng mới và sao chép giá trị của các trường primitive (kiểu nguyên thủy). Đối với các trường reference (kiểu tham chiếu), shallow copy chỉ sao chép địa chỉ bộ nhớ, nghĩa là đối tượng mới và đối tượng gốc sẽ cùng tham chiếu đến một đối tượng con.

  • Ưu điểm: Tốc độ nhanh, tốn ít bộ nhớ
  • Nhược điểm: Thay đổi đối tượng con sẽ ảnh hưởng đến cả hai đối tượng

Deep Copy (Sao chép sâu)

Deep copy tạo ra một bản sao hoàn toàn độc lập, bao gồm cả việc sao chép tất cả các đối tượng con bên trong. Mọi thay đổi trên bản sao sẽ không ảnh hưởng đến đối tượng gốc.

  • Ưu điểm: Đảm bảo tính độc lập dữ liệu hoàn toàn
  • Nhược điểm: Hiệu suất thấp hơn, code phức tạp hơn

2. So sánh các phương pháp triển khai

Loại sao chépPhương phápTình huống sử dụng
Shallow CopySử dụng clone() mặc địnhKhi đối tượng con không cần thay đổi độc lập
Deep CopyClone đệ quy hoặc SerializationKhi cần bản sao độc lập hoàn toàn

3. Ví dụ triển khai chi tiết

3.1 Shallow Copy

Ví dụ dưới đây minh họa cách triển khai shallow copy sử dụng interface Cloneable:

class Country implements Cloneable {
    private String name;
    
    public Country(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Employee implements Cloneable {
    private String id;
    private String fullName;
    private Country country;
    
    public Employee(String id, String fullName, Country country) {
        this.id = id;
        this.fullName = fullName;
        this.country = country;
    }
    
    public String getId() {
        return id;
    }
    
    public String getFullName() {
        return fullName;
    }
    
    public Country getCountry() {
        return country;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // Chỉ sao chép tham chiếu country
    }
}

// Cách sử dụng
Country vn = new Country("Việt Nam");
Employee emp1 = new Employee("E001", "Nguyễn Văn A", vn);
Employee emp2 = (Employee) emp1.clone();

emp2.getCountry().setName("Mỹ");
System.out.println(emp1.getCountry().getName()); // Kết quả: "Mỹ"

Như ví dụ trên, khi thay đổi country của emp2, emp1 cũng bị ảnh hưởng vì cả hai cùng tham chiếu đến một đối tượng Country.

3.2 Deep Copy với Recursive Clone

Để tạo bản sao độc lập, ta cần override phương thức clone() để sao chép cả các đối tượng con:

class Employee implements Cloneable {
    private String id;
    private String fullName;
    private Country country;
    
    public Employee(String id, String fullName, Country country) {
        this.id = id;
        this.fullName = fullName;
        this.country = country;
    }
    
    // Getters và setters...
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Employee cloned = (Employee) super.clone();
        // Sao chép sâu đối tượng Country
        cloned.country = (Country) this.country.clone();
        return cloned;
    }
}

// Cách sử dụng
Country vn = new Country("Việt Nam");
Employee emp1 = new Employee("E001", "Nguyễn Văn A", vn);
Employee emp2 = (Employee) emp1.clone();

emp2.getCountry().setName("Mỹ");
System.out.println(emp1.getCountry().getName()); // Kết quả: "Việt Nam"

3.3 Deep Copy với Serialization

Phương pháp serialization giúp đơn giản hóa việc deep copy mà không cần manually clone từng đối tượng:

import java.io.*;

public class ObjectCloner {
    
    public static <T extends Serializable> T deepCopy(T object) {
        try {
            // Ghi đối tượng vào ByteArrayOutputStream
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(object);
            oos.close();
            
            // Đọc đối tượng từ ByteArrayInputStream
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            T clonedObject = (T) ois.readObject();
            ois.close();
            
            return clonedObject;
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Lỗi khi deep copy: " + e.getMessage(), e);
        }
    }
}

// Cách sử dụng
Country vn = new Country("Việt Nam");
Employee emp1 = new Employee("E001", "Nguyễn Văn A", vn);
Employee emp2 = ObjectCloner.deepCopy(emp1);

emp2.getCountry().setName("Mỹ");
System.out.println(emp1.getCountry().getName()); // Kết quả: "Việt Nam"

Lưu ý: Tất cả các class cần sao chép phải implement Serializable, và các trường marked là transient sẽ không được sao chép.

4. Tình huống sử dụng thực tế

4.1 Quản lý Game State (Deep Copy)

Trong các ứng dụng game, tính năng save/load game đòi hỏi trạng thái hoàn toàn độc lập:

class GameCharacter implements Cloneable {
    private String name;
    private int health;
    private List<Item> inventory;
    
    // Triển khai deep clone để lưu trạng thái độc lập
    @Override
    protected Object clone() throws CloneNotSupportedException {
        GameCharacter cloned = (GameCharacter) super.clone();
        cloned.inventory = new ArrayList<>(this.inventory);
        return cloned;
    }
}

4.2 Cache Configuration (Shallow Copy)

Khi nhiều request cần đọc cùng một cấu hình không thay đổi:

class ConfigTemplate implements Cloneable {
    private Map<String, String> settings;
    
    @Override
    protected Object clone() {
        return super.clone(); // Chia sẻ reference, tiết kiệm bộ nhớ
    }
}

4.3 Transaction Snapshot (Deep Copy)

Trong hệ thống e-commerce, khi tạo snapshot của đơn hàng trước khi xử lý thanh toán:

class OrderSnapshot {
    public static Order createSnapshot(Order originalOrder) {
        return ObjectCloner.deepCopy(originalOrder);
    }
}

5. Các lưu ý quan trọng

  • Hiệu suất: Deep copy đệ quy hoặc serialization có chi phí cao, đặc biệt với đối tượng có nhiều tầng lồng nhau
  • Xử lý Circular Reference: Cần tránh vòng lặp vô hạn khi clone đệ quy, có thể sử dụng Map để cache các object đã clone
  • Yêu cầu với Serialization: Tất cả class phải implement Serializable, transient fields sẽ bị bỏ qua
  • Alternative approaches: Có thể sử dụng các thư viện như Apache Commons Lang, Gson, Jackson để đơn giản hóa deep copy

6. Kết luận

Việc lựa chọn giữa shallow copy và deep copy phụ thuộc vào yêu cầu nghiệp vụ cụ thể:

  • Sử dụng shallow copy khi cần hiệu suất cao và đối tượng con không cần thay đổi độc lập
  • Sử dụng deep copy khi cần đảm bảo tính toàn vẹn dữ liệu và isolation giữa các đối tượng

Trong thực tế, có thể kết hợp cả hai phương pháp: shallow copy một số trường và deep copy những trường cần thiết để tối ưu hóa hiệu suất và bộ nhớ.

Thẻ: Java clone deep-copy shallow-copy object-copy

Đăng vào ngày 15 tháng 7 lúc 09:07