Nhân bản là một mô hình lập trình cơ bản. Thực tế là Java có thể đã triển khai nó kém theo nhiều cách không làm giảm nhu cầu nhân bản. Và, rất dễ dàng để thực hiện nhân bản sẽ hoạt động theo bất kỳ cách nào bạn muốn nó hoạt động, nông, sâu, hỗn hợp, bất cứ điều gì. Bạn thậm chí có thể sử dụng bản sao tên cho hàm và không triển khai Bản sao nếu bạn muốn.
Giả sử tôi có các lớp A, B và C, trong đó B và C bắt nguồn từ A. Nếu tôi có danh sách các đối tượng kiểu A như thế này:
ArrayList<A> list1;
Bây giờ, danh sách đó có thể chứa các đối tượng kiểu A, B hoặc C. Bạn không biết các đối tượng đó là kiểu gì. Vì vậy, bạn không thể sao chép danh sách như thế này:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(new A(a));
}
Nếu đối tượng thực sự thuộc loại B hoặc C, bạn sẽ không nhận được bản sao phù hợp. Và, nếu A là trừu tượng thì sao? Bây giờ, một số người đã đề xuất điều này:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
if(a instanceof A) {
list2.add(new A(a));
} else if(a instanceof B) {
list2.add(new B(a));
} else if(a instanceof C) {
list2.add(new C(a));
}
}
Đây là một ý tưởng rất, rất tệ. Điều gì sẽ xảy ra nếu bạn thêm một kiểu dẫn xuất mới? Điều gì sẽ xảy ra nếu B hoặc C nằm trong một gói khác và bạn không có quyền truy cập vào chúng trong lớp này?
Những gì bạn muốn làm là:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(a.clone());
}
Rất nhiều người đã chỉ ra lý do tại sao việc triển khai Java cơ bản của bản sao lại có vấn đề. Nhưng, nó dễ dàng vượt qua theo cách này:
Trong lớp A:
public A clone() {
return new A(this);
}
Ở lớp B:
@Override
public B clone() {
return new B(this);
}
Trong lớp C:
@Override
public C clone() {
return new C(this):
}
Tôi không triển khai Cloneable, chỉ sử dụng cùng một tên hàm. Nếu bạn không thích điều đó, hãy đặt tên khác.