Bỏ qua để đến nội dung

Shallow copy vs Deep copy

Khi sao chép một đối tượng chứa các field kiểu tham chiếu (như mảng, List, hay đối tượng khác), sao chép “nông” (shallow) chỉ sao chép các tham chiếu, không sao chép nội dung mà chúng trỏ đến - hai đối tượng “tưởng như độc lập” thực ra vẫn chia sẻ chung dữ liệu bên trong:

public class Team {
List<String> members;
Team(List<String> members) { this.members = members; }
}
Team original = new Team(new ArrayList<>(List.of("An", "Bình")));
Team copy = new Team(original.members); // "sao chép nông" - chỉ sao chép THAM CHIẾU đến list
copy.members.add("Chi"); // sửa qua copy...
System.out.println(original.members); // [An, Bình, Chi] - original CŨNG bị ảnh hưởng!

new Team(original.members) chỉ gán tham chiếu của members cho copy - không tạo một List mới. Cả original.memberscopy.members cùng trỏ đến một ArrayList duy nhất trên heap - sửa qua bên nào cũng ảnh hưởng đến cả hai. Đây là hệ quả trực tiếp của việc Java truyền và gán tham chiếu theo giá trị (xem trang “Tham số và đối số”) - gán một biến tham chiếu không sao chép nội dung đối tượng.

Vấn đề tương tự cũng xảy ra với clone() mặc định của Object - nó chỉ tạo shallow copy, sao chép từng field nguyên trạng (bao gồm cả tham chiếu) chứ không sao chép sâu nội dung các đối tượng mà field đó trỏ tới.

Deep copy (sao chép sâu): tạo bản sao mới hoàn toàn cho cả các đối tượng bên trong, không chỉ sao chép tham chiếu:

public class Team {
List<String> members;
Team(List<String> members) {
this.members = new ArrayList<>(members); // tạo LIST MỚI, sao chép nội dung
}
}
Team original = new Team(new ArrayList<>(List.of("An", "Bình")));
Team copy = new Team(original.members); // constructor đã tự tạo list mới bên trong
copy.members.add("Chi");
System.out.println(original.members); // [An, Bình] - KHÔNG bị ảnh hưởng, độc lập hoàn toàn!

Với cấu trúc dữ liệu lồng nhau nhiều tầng (list chứa object, object chứa list khác…), cần sao chép đệ quy ở mọi tầng để đảm bảo deep copy thực sự - hoặc dùng thư viện hỗ trợ (như serialize/deserialize, hay các thư viện bên thứ ba chuyên biệt).

Shallow copy: chỉ sao chép tham chiếu - bản sao và bản gốc vẫn chia sẻ chung dữ liệu bên trong (mảng, List, object con). Deep copy: tạo bản sao độc lập hoàn toàn cho cả dữ liệu bên trong, thường phải tự viết logic sao chép từng tầng (ví dụ new ArrayList<>(list) cho tầng đầu tiên).

  • Luôn cân nhắc: đối tượng chứa field tham chiếu có cần “độc lập thật sự” với bản gốc hay không, trước khi coi một phép gán/constructor là “đã sao chép xong”