2012-12-02 44 views
10

Tôi đã đọc trên thực hiện một bản sao sâu của một mảng chính xác, tuy nhiên tôi đã nhầm lẫn về cách thực hiện #clone(). Nó là một thành viên của lớp java.lang.Object, nhưng nếu bạn đọc javadocs:Tại sao #clone() không có trong giao diện Cloneable?

Thứ nhất, nếu lớp của đối tượng này không thực hiện giao diện Cloneable, sau đó một CloneNotSupportedException được ném.

Vậy tại sao xác định phương thức clone ở nơi đầu tiên? Chắc chắn nếu một phương thức chỉ có thể được sử dụng khi một giao diện có mặt, bạn sẽ đặt phương thức đó vào giao diện. Giao diện Cloneable trống; nó chỉ là một giao diện điểm đánh dấu được sử dụng bởi Java để đảm bảo rằng việc sử dụng phương thức clone là hợp pháp.

Làm theo cách này cũng loại bỏ khả năng để làm cho việc sử dụng Generics để đảm bảo an toàn kiểu:

class Foo implements Cloneable { // Valid. 
    @Override 
    public Object clone() throws CloneNotSupportedException { 
     // ... 
    } 
} 

class TypeSafeFoo implements Cloneable<TypeSafeFoo> { // Not valid. 
    @Override 
    public TypeSafeFoo clone() throws CloneNotSupportedException { 
     // ... 
    } 
} 

Tại sao Java thực hiện nó theo cách này? Tôi chắc rằng họ có lý do chính đáng, nhưng tôi không thể hình dung ra được.

+3

Trên thực tế đây là một lỗ hổng thiết kế như đã nói bởi Joshua Bloch –

+0

Đây là một trong nhiều lý do nhân bản được coi là bị hỏng. –

Trả lời

14

Hợp đồng nhân bản trong Java ra lệnh rằng mỗi lần triển khai clone trước tiên phải có được bản sao được sao chép từ super.clone(). Điều này tạo ra một chuỗi luôn kết thúc bằng lời gọi đến Object.clone và phương thức đó chứa mã cấp độ gốc "huyền diệu" tạo bản sao nhị phân của nguyên mẫu cơ bản struct đại diện cho đối tượng Java. Nếu cơ chế này không tồn tại, clone sẽ không được đa hình: phương pháp Object.clone tạo ra một thể hiện của bất kỳ lớp nào nó được gọi; điều này không thể được sao chép mà không có mã nguồn gốc.

Đây là lý do tại sao phương pháp Object.clone không thể tránh được. Cloneablecó thể có chứa phương thức clone nhưng sẽ tạo ra các vấn đề liên quan đến mệnh đề throws. Cách nó đứng bạn được tự do tuyên bố clone không có ngoại lệ tuyên bố, hoặc tuyên bố ngoại lệ tùy ý. Sự linh hoạt này sẽ không thể thực hiện được nếu phương thức này đã được khai báo trong giao diện.

Hãy nhớ rằng Generics sẽ ít được sử dụng để nhân bản: hãy tưởng tượng protected T clone() trong Object: nơi nào sẽ xuất hiện T? Chúng ta có cần Object<T> và buộc mỗi và mọi lớp trong vũ trụ Java được tham số hóa trên chính nó, và tất cả điều này chỉ để làm cho cơ chế bán không dùng này này hoạt động tốt hơn một chút?Giữ cũng nhớ rằng mã này là hoàn toàn hợp pháp:

public class TheMightyOne implements Cloneable { 
    @Override public TheMightyOne clone() { 
    return (TheMightyOne) super.clone(); 
    } 
} 

Bạn có thể gọi nó là:

TheMightyOne one = new TheMightyOne(); 
TheMightyOne two = one.clone(); // do downcasts needed 
+0

Hầu hết điều này có thể tránh được bằng cách làm cho phương pháp sao chép ma thuật tĩnh không? 'Object.clone (mục có thể theo dõi)'. Sau đó, bất cứ điều gì thực hiện 'Cloneable' sẽ chuyển' this' sang 'Object.clone'. Điều này có thể dễ dàng làm việc với generics quá. –

4

Để giải quyết việc tạo bản sao và sao chép trường cơ bản, bản sao cần kế thừa triển khai phương pháp. Một lớp Cloneable có thể xuất hiện ở bất kỳ đâu trong hệ thống phân cấp và có thể cần mở rộng một lớp cha cụ thể để thực hiện công việc chính của nó. Lớp cha duy nhất mà từ đó tất cả các lớp Cloneable có thể kế thừa thực hiện là Object.

Nhân bản được xác định từ lâu trước khi dùng thuốc generic.

0

clone() phương pháp là không cần thiết, mỗi đối tượng có thể gọi Object.clone phương pháp mà theo phản ánh, vì vậy một đối tượng có thể được nhân bản vô tính phụ thuộc vào việc có triển khai giao diện Cloneable hay không. Đây là vì lý do an ninh. Bạn chỉ có thể sao chép một đối tượng mà thực hiện Cloneable bởi util này:

@SuppressWarnings("unchecked") 
public static <T extends Cloneable> T clone(Cloneable obj) { 
    T rtn = null; 
    try { 
     Method method = Object.class.getDeclaredMethod("clone"); 
     method.setAccessible(true); 
     rtn = (T) method.invoke(obj); 
    } catch (Throwable t) { 
     t.printStackTrace(); 
    } 
    return rtn; 
} 
Các vấn đề liên quan