Giới thiệu về Java có thể sao chép


95

Tôi đã tìm kiếm một số hướng dẫn giải thích về Java Cloneable, nhưng không nhận được bất kỳ liên kết tốt nào và Stack Overflow dù sao cũng đang trở thành lựa chọn rõ ràng hơn.

Tôi muốn biết những điều sau:

  1. Cloneablecó nghĩa là chúng ta có thể có một bản sao hoặc một bản sao của các đối tượng, bằng cách triển khai Cloneablegiao diện. Những thuận lợi và khó khăn của việc làm đó là gì?
  2. Làm thế nào để nhân bản đệ quy xảy ra nếu đối tượng là một đối tượng tổng hợp?

2
Những thuận lợi và khó khăn so với những gì?
Galactus

4
Tôi đọc điều đó có nghĩa là lợi thế của một lớp có thể Nhân bản so với không. Không chắc có thể hiểu như thế nào khác: S
allyourcode

Câu trả lời:


159

Điều đầu tiên bạn nên biết Cloneablelà - không sử dụng nó.

Rất khó để thực hiện nhân bản với Cloneablequyền, và công sức bỏ ra là không đáng.

Thay vì điều đó, hãy sử dụng một số tùy chọn khác, như apache-commons SerializationUtils(bản sao sâu) hoặc BeanUtils(bản sao nông) hoặc đơn giản là sử dụng một phương thức tạo bản sao.

Xem tại đây để biết quan điểm của Josh Bloch về nhân bản với Cloneable, điều này giải thích nhiều nhược điểm của phương pháp này. ( Joshua Bloch là một nhân viên của Sun và đã dẫn đầu sự phát triển của nhiều tính năng Java.)


1
Tôi đã liên kết các từ của Bloch (thay vì trích dẫn chúng)
Bozho

3
Lưu ý rằng Block nói rằng không sử dụng Cloneable. Anh ấy không nói rằng không sử dụng nhân bản (hoặc ít nhất tôi hy vọng là không). Có nhiều cách để thực hiện nhân bản đơn giản mà hiệu quả hơn nhiều so với các lớp như SerializationUtils hoặc BeanUtils sử dụng phản xạ. Xem bài đăng của tôi dưới đây để biết ví dụ.
Charles

Đâu là lựa chọn thay thế cho hàm tạo bản sao khi xác định giao diện? chỉ cần thêm một phương pháp sao chép?
Benez

@benez Tôi sẽ nói có. vì java-8 bạn có thể có staticcác phương thức trong giao diện, vì vậy chỉ cần cung cấp một static WhatEverTheInterface copy(WhatEverTheInterface initial)? nhưng tôi tự hỏi điều này mang lại cho bạn điều gì, vì bạn sao chép các trường từ một đối tượng trong khi nhân bản, nhưng một giao diện chỉ xác định các phương thức. quan tâm để giải thích?
Eugene

40

Bản thân cloneable rất tiếc chỉ là một giao diện đánh dấu, nghĩa là: nó không định nghĩa phương thức clone ().

Những gì thực hiện là thay đổi hành vi của phương thức Object.clone () được bảo vệ, phương thức này sẽ ném ra một CloneNotSupportedException cho các lớp không triển khai Cloneable và thực hiện một bản sao nông dành cho thành viên đối với các lớp có.

Ngay cả khi đây là hành vi bạn đang tìm kiếm, bạn vẫn cần triển khai phương thức clone () của riêng mình để đặt nó ở chế độ công khai.

Khi triển khai bản sao của riêng bạn (), ý tưởng là bắt đầu với đối tượng được tạo bởi super.clone (), đối tượng này được đảm bảo là thuộc lớp chính xác, và sau đó thực hiện bất kỳ tập hợp trường bổ sung nào trong trường hợp bản sao nông không phải là gì. bạn muốn. Việc gọi một hàm tạo từ clone () sẽ có vấn đề vì điều này sẽ phá vỡ tính kế thừa trong trường hợp một lớp con muốn thêm logic có thể sao chép bổ sung của chính nó; nếu nó được gọi super.clone () nó sẽ nhận được một đối tượng không đúng lớp trong trường hợp này.

Tuy nhiên, cách tiếp cận này bỏ qua bất kỳ logic nào có thể được xác định trong các hàm tạo của bạn, điều này có thể có vấn đề.

Một vấn đề khác là bất kỳ lớp con nào quên ghi đè clone () sẽ tự động kế thừa bản sao nông mặc định, có thể không phải là thứ bạn muốn trong trường hợp trạng thái có thể thay đổi (hiện sẽ được chia sẻ giữa nguồn và bản sao).

Hầu hết các nhà phát triển không sử dụng Cloneable vì những lý do này và thay vào đó, chỉ cần triển khai một hàm tạo bản sao.

Để biết thêm thông tin và những cạm bẫy tiềm ẩn của Khả năng sao chép, tôi thực sự giới thiệu cuốn sách Java hiệu quả của Joshua Bloch


12
  1. Nhân bản gọi một cách bổ sung ngôn ngữ để xây dựng các đối tượng - không có hàm tạo.
  2. Nhân bản yêu cầu bạn xử lý bằng cách nào đó bằng CloneNotSupportedException - hoặc làm phiền mã máy khách để xử lý nó.
  3. Lợi ích là rất nhỏ - bạn không cần phải viết một hàm tạo sao chép theo cách thủ công.

Vì vậy, hãy sử dụng Cloneable một cách thận trọng. Nó không mang lại cho bạn lợi ích đầy đủ so với nỗ lực bạn cần áp dụng để làm mọi thứ đúng.


Như Bozho đã nói, đừng sử dụng Cloneable. Thay vào đó, hãy sử dụng một copy-constructor. javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane, điều gì sẽ xảy ra nếu bạn không biết loại đối tượng cần sao chép, làm thế nào bạn biết hàm tạo bản sao của lớp nào để gọi?
Steve Kuo

@Steve: Tôi không theo dõi. Nếu bạn định nhân bản một đối tượng, tôi cho rằng bạn đã biết nó thuộc loại gì - sau cùng, bạn đã có trong tay đối tượng mà bạn đang lên kế hoạch nhân bản. Và nếu có một tình huống trong đó đối tượng của bạn bị mất kiểu cụ thể thành một kiểu chung chung hơn, bạn không thể đánh giá nó bằng cách sử dụng 'trường hợp' đơn giản sao ???
Bane

4
@Bane: Giả sử bạn có một danh sách tất cả các đối tượng bắt nguồn từ kiểu A, có thể với 10 kiểu khác nhau. Bạn không biết loại của mỗi đối tượng là gì. Sử dụng instanceof trong trường hợp này là một ý kiến ​​RẤT tồi. Nếu bạn thêm một kiểu khác, mỗi khi bạn làm điều này, bạn sẽ phải thêm một thử nghiệm instanceof khác. Và, điều gì sẽ xảy ra nếu các lớp dẫn xuất nằm trong một gói khác mà bạn thậm chí không thể truy cập? Nhân bản là một mô hình phổ biến. Có, việc triển khai java là không tốt, nhưng có nhiều cách xung quanh nó sẽ hoạt động tốt. Một hàm tạo sao chép không phải là một hoạt động tương đương.
Charles

@Charles: Trong trường hợp không có ví dụ chi tiết và thiếu kinh nghiệm gần đây để giải quyết loại vấn đề này, tôi sẽ phải chuyển sang Bloch. Mục # 11. Nó dài và hơi khó đọc, nhưng về cơ bản nó nói "tránh sao chép bất cứ khi nào bạn có thể, các hàm tạo bản sao là bạn của bạn".
Bane

7

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.


Tôi chỉ thấy điều này sau khi trả lời bình luận của bạn trong câu trả lời riêng biệt; Tôi biết bây giờ bạn đang đi đâu, tuy nhiên có 2 điều: 1) OP đã hỏi cụ thể về việc sử dụng Cloneable (không phải về khái niệm chung về nhân bản), và 2) bạn đang chia sẻ một chút ở đây khi cố gắng phân biệt giữa một copy-constructor và khái niệm chung về nhân bản. Ý tưởng bạn truyền đạt ở đây là hợp lệ, nhưng ở gốc rễ, bạn chỉ đang sử dụng một hàm tạo bản sao. ;)
Bane

Mặc dù tôi muốn nói rằng tôi đồng ý với cách tiếp cận của bạn ở đây, đó là bao gồm A # copyMethod (), thay vì buộc người dùng gọi trực tiếp hàm tạo bản sao.
Bane

5

A) Không có nhiều lợi thế của bản sao so với phương thức tạo bản sao. Có lẽ cái lớn nhất là khả năng tạo một đối tượng mới có cùng kiểu động chính xác (giả sử kiểu được khai báo là có thể sao chép và có phương thức nhân bản công khai).

B) Bản sao mặc định tạo ra một bản sao cạn và nó sẽ vẫn là một bản sao cạn trừ khi việc triển khai bản sao của bạn thay đổi điều đó. Điều này có thể khó khăn, đặc biệt nếu lớp của bạn có các trường cuối cùng

Bozho nói đúng, rất khó để làm đúng. Một nhà máy / nhà máy xây dựng bản sao sẽ phục vụ hầu hết các nhu cầu.


0

Nhược điểm của Cloneable là gì?

Sao chép rất nguy hiểm nếu đối tượng mà bạn đang sao chép có thành phần. Bạn cần nghĩ đến tác dụng phụ có thể xảy ra dưới đây trong trường hợp này vì sao chép tạo ra bản sao nông:

Giả sử bạn có một đối tượng để xử lý thao tác liên quan đến db. Giả sử, đối tượng đó có Connectionđối tượng là một trong các thuộc tính.

Vì vậy, khi ai đó tạo bản sao của originalObjectđối tượng được tạo, giả sử cloneObject,. Đây originalObjectcloneObjectgiữ cùng một tham chiếu cho Connectionđối tượng.

Giả sử originalObjectđóng Connectionđối tượng, vì vậy bây giờ cloneObjectsẽ không hoạt động vì connectionđối tượng được chia sẻ giữa chúng và nó đã được đóng thực tế bởi originalObject.

Vấn đề tương tự có thể xảy ra nếu giả sử bạn muốn sao chép một đối tượng có IOStream làm thuộc tính.

Làm thế nào để nhân bản đệ quy xảy ra nếu đối tượng là một đối tượng tổng hợp?

Cloneable thực hiện sao chép nông. Có nghĩa là dữ liệu của đối tượng gốc và đối tượng nhân bản sẽ trỏ đến cùng một tham chiếu / bộ nhớ. ngược lại trong trường hợp sao chép sâu, dữ liệu từ bộ nhớ của đối tượng gốc được sao chép vào bộ nhớ của đối tượng nhân bản.


Đoạn cuối của bạn rất bối rối. Cloneablekhông thực hiện một bản sao, Object.clonekhông. "Dữ liệu từ bộ nhớ của đối tượng gốc được sao chép vào bộ nhớ của đối tượng nhân bản" chính xác là những gì Object.clonehiện. Bạn cần nói về bộ nhớ của các đối tượng được tham chiếu để mô tả việc sao chép sâu.
aioobe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.