Lớp đối tượng có một phương thức clone() được bảo vệ được khai báo để cho phép tất cả các lớp tạo một bản sao của chính nó khi cần. Phương thức clone() thường được sử dụng khi cần một thể hiện mới của lớp trong khi vẫn duy trì trạng thái giống như đối tượng gốc. Bất kỳ lớp nào muốn bật chức năng clone phải triển khai giao diện đánh dấu Cloneable.
Nếu một lớp triển khai Cloneable không ghi đè phương thức Object.clone(), phương thức Object.clone() sẽ được gọi để chỉ tạo một bản sao nhị phân của bản gốc, tương đương với bản sao nông, với tất cả các đối tượng được tham chiếu vẫn giữ nguyên. Điều này có nghĩa là tất cả các tham chiếu bao gồm cả các tham chiếu riêng tư trong đối tượng gốc sẽ được sao chép vào đối tượng được sao chép. Object.clone() là một phương thức gốc và nó thực hiện sao chép nông. Để biết thêm thông tin, vui lòng xem mã nguồn Object.clone().
Nếu một lớp triển khai Cloneable ghi đè phương thức Object.clone(), thường thì super.clone() sẽ được gọi trước để tạo một bản sao nhị phân của đối tượng gốc và sau đó sao chép sâu sẽ được thực hiện tương ứng dựa trên bản sao nhị phân đó. Xem ví dụ bên dưới:
public class CloneTest implements Cloneable { private byte[] a = {1, 2, 3, 4, 5}; private byte[] b = {5, 4, 3, 2, 1}; public CloneTest clone(){ CloneTest that = null; try{ that = (CloneTest)super.clone(); //Tạo một bản sao nhị phân that.b = this.b.clone(); //Thực hiện thao tác tùy chỉnh return that; } catch (CloneNotSupportedException ex){ ex.printStackTrace(); } return that; } public byte[] getA(){ return this.a; } public byte[] getB(){ return this.b; } public static void main(String[] args){ CloneTest original = new CloneTest(); CloneTest cloned = original.clone(); //Về original.a System.out.println("original.a == cloned.a : " + (original.getA() == cloned.getA())); System.out.println("cloned.a[2] = " + cloned.getA()[2]); //Sửa đổi original.a[2] original.getA()[2] = 10; System.out.println("cloned.a[2] = " + cloned.getA()[2]); //Về original.b System.out.println("original.b == cloned.b : " + (original.getB() == cloned.getB())); System.out.println("cloned.b[2] = " + cloned.getB()[2]); //Sửa đổi original.b[2] original.getB()[2] = 10; System.out.println("cloned.b[2] = " + cloned.getB()[2]); } }
Bạn có bối rối về phương thức super.clone()? Tại sao super.clone() có thể được ép kiểu thành CloneTest? Thông thường super có nghĩa là lớp cha, điều đó có nghĩa là chúng ta chỉ nhận được một bản sao của thể hiện lớp cha, phải không? Sau đó, đối tượng được ép kiểu xuống đối tượng mà không nên chính xác, phải không? Thực tế là super.clone() sẽ trả về một đối tượng gọi phương thức super.clone(). Để hiểu tại sao, vui lòng đọc kỹ về những gì phương thức Object.clone() làm. Việc triển khai clone()
trong Object
kiểm tra xem lớp thực tế có triển khai Cloneable
hay không và tạo một thể hiện của lớp thực tế đó. Điều này có nghĩa là mối quan hệ dưới đây nên chính xác:
x.clone() != x x.clone().getClass() == x.getClass()
Một lưu ý quan trọng khác về clone() là bất kỳ đối tượng có thể thay đổi nào cũng nên được sao chép rõ ràng khi ghi đè phương thức clone(). Ví dụ trên có một lỗi sẽ gây ra sự cố. Mảng đối tượng có thể thay đổi a không được sao chép rõ ràng nên đối tượng được sao chép sẽ chia sẻ cùng một tham chiếu mảng với đối tượng gốc. Nếu một phần tử trong mảng a bị thay đổi, thay đổi này cũng sẽ phản ánh trên đối tượng được sao chép.
Kết quả từ việc thực thi chương trình cho thấy chính xác hành vi này:
original.a == cloned.a : true cloned.a[2] = 3 cloned.a[2] = 10 original.b == cloned.b : false cloned.b[2] = 3 cloned.b[2] = 3
Từ đầu ra, original.a == cloned.a : true có nghĩa là tham chiếu đến mảng a là giống nhau trong đối tượng gốc và đối tượng được sao chép. Và cloned.a[2] cũng được thay đổi thành 10.
Để giảm thiểu vấn đề, cần thêm that.a = this.a.clone(); vào phương thức clone().
Một lời khuyên chung về việc sử dụng clone() là cực kỳ cẩn thận khi ghi đè phương thức clone(). Kiểm tra mọi đối tượng có thể thay đổi trong lớp và xem liệu có cần tạo một bản sao sâu của nó hay không. Không sử dụng clone() bất cứ khi nào có thể.