Java: giải pháp được đề xuất để nhân bản sâu / sao chép một cá thể


176

Tôi tự hỏi nếu có một cách được đề xuất để thực hiện bản sao / bản sao sâu trong java.

Tôi có 3 giải pháp trong đầu, nhưng tôi có thể bỏ lỡ một số, và tôi muốn có ý kiến ​​của bạn

chỉnh sửa: bao gồm đề xuất Bohzo và câu hỏi tinh chỉnh: đó là về nhân bản sâu hơn là nhân bản nông.

Tự làm

mã bản sao bằng các thuộc tính tay sau các thuộc tính và kiểm tra xem các thể hiện có thể thay đổi cũng được nhân bản không.
pro:
- kiểm soát những gì sẽ được thực hiện
-
nhược điểm thực hiện nhanh :
- tẻ nhạt để viết và bảo trì
- dễ bị lỗi (sao chép / dán thất bại, thiếu tài sản, thuộc tính có thể thay đổi được gán lại)

Sử dụng sự phản chiếu:

Với các công cụ phản chiếu của riêng bạn hoặc với một người trợ giúp bên ngoài (như jakarta common-bean), thật dễ dàng để viết một phương thức sao chép chung sẽ thực hiện công việc trong một dòng.
pro:
- dễ viết
- không có
nhược điểm bảo trì :
- ít kiểm soát những gì xảy ra
- dễ bị lỗi với đối tượng có thể thay đổi nếu công cụ phản chiếu không sao chép các đối tượng phụ quá
- thực thi chậm hơn

Sử dụng khung nhân bản:

Sử dụng một khung làm việc đó cho bạn, như:
Commons-lang serializationUtils
Thư viện nhân bản sâu Java
Dozer
Kryo

pro:
- giống như sự phản chiếu
- kiểm soát nhiều hơn những gì sẽ được nhân bản chính xác.
Nhược điểm:
- mọi trường hợp có thể thay đổi được sao chép hoàn toàn, ngay cả ở cuối phân cấp
- có thể rất chậm để thực thi

Sử dụng công cụ mã byte để viết bản sao khi chạy

javassit , BCEL hoặc cglib có thể được sử dụng để tạo ra một bản sao chuyên dụng nhanh như một tay viết. Ai đó biết một lib sử dụng một trong những công cụ này cho mục đích này?

Những gì tôi đã bỏ lỡ ở đây?
Bạn muốn giới thiệu cái nào?

Cảm ơn.


1
rõ ràng Thư viện nhân bản sâu Java đã chuyển đến đây: code.google.com/p/claming
Mr_and_Mrs_D

Câu trả lời:


155

Để nhân bản sâu (nhân bản toàn bộ hệ thống phân cấp đối tượng):

  • commons-lang serializationUtils - sử dụng tuần tự hóa - nếu tất cả các lớp nằm trong sự kiểm soát của bạn và bạn có thể buộc thực hiện Serializable.

  • Thư viện nhân bản sâu Java - sử dụng sự phản chiếu - trong trường hợp khi các lớp hoặc các đối tượng bạn muốn sao chép nằm ngoài tầm kiểm soát của bạn (thư viện bên thứ 3) và bạn không thể thực hiện chúng Serializablehoặc trong trường hợp bạn không muốn thực hiện Serializable.

Đối với nhân bản nông (chỉ nhân bản các thuộc tính cấp đầu tiên):

Tôi đã cố tình bỏ qua tùy chọn "tự làm" - các API ở trên cung cấp khả năng kiểm soát tốt đối với những gì nên và không sao chép (ví dụ: sử dụng transient, hoặc String[] ignoreProperties), vì vậy việc phát minh lại bánh xe không được ưa thích.


Cảm ơn Bozho, điều đó có giá trị. Và tôi đồng ý với bạn về tùy chọn DIY! Bạn đã bao giờ thử nối tiếp commons và / hoặc lib nhân bản sâu chưa? Những gì về sự hoàn hảo?
Guillaume

vâng, tôi đã sử dụng tất cả các tùy chọn ở trên, vì những lý do trên :) chỉ thư viện nhân bản có một số vấn đề khi các proxy CGLIB có liên quan và đã bỏ lỡ một số chức năng mong muốn, nhưng tôi nghĩ rằng nên khắc phục ngay.
Bozho

Xin chào, nếu Thực thể của tôi được đính kèm và tôi có những thứ lười biếng, liệu serializationUtils có kiểm tra cơ sở dữ liệu cho các thuộc tính lười biếng không? Vì đây là những gì tôi muốn, và nó không!
Cosmin Cosmin

nếu bạn có một phiên hoạt động - vâng, nó có.
Bozho

@Bozho Vậy ý bạn là nếu tất cả các đối tượng trong bean đang thực hiện tuần tự hóa, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) sẽ sao chép sâu?
nhảy

36

Cuốn sách của Joshua Bloch có cả một chương có tựa đề "Mục 10: Ghi đè bản sao một cách thận trọng", trong đó ông đi vào lý do tại sao việc ghi đè lên bản sao phần lớn là một ý tưởng tồi bởi vì thông số Java cho nó tạo ra nhiều vấn đề.

Ông cung cấp một vài lựa chọn thay thế:

  • Sử dụng mẫu nhà máy thay cho nhà xây dựng:

         public static Yum newInstance(Yum yum);
  • Sử dụng một hàm tạo sao chép:

         public Yum(Yum yum);

Tất cả các lớp bộ sưu tập trong Java đều hỗ trợ hàm tạo sao chép (ví dụ ArrayList mới (l);)


1
Đã đồng ý. Trong dự án của tôi, tôi đã định nghĩa một Copyablegiao diện có chứa một getCopy()phương thức. Chỉ cần sử dụng mẫu nguyên mẫu bằng tay.
gpampara

Vâng, tôi không hỏi về giao diện có thể sao chép, nhưng làm thế nào để thực hiện thao tác sao chép / sao chép sâu. Với một nhà xây dựng hoặc một nhà máy, bạn vẫn cần tạo cá thể mới từ nguồn của mình.
Guillaume

@Guillaume Tôi nghĩ bạn cần cẩn thận trong việc sử dụng các từ sao chép / sao chép sâu. Bản sao và sao chép trong java KHÔNG có nghĩa là điều tương tự. Thông số kỹ thuật Java có nhiều điều để nói về điều này .... Tôi nghĩ rằng bạn muốn có một bản sao sâu sắc từ những gì tôi có thể nói.
LeWoody

OK Thông số kỹ thuật Java chính xác về bản sao là gì ... Nhưng chúng ta cũng có thể nói về bản sao theo nghĩa phổ biến hơn ... Ví dụ: một trong những lib được đề xuất bởi bohzo có tên là 'Thư viện nhân bản sâu Java' ...
Guillaume

2
@LWoodyiii newInstance()phương pháp này và nhà Yumxây dựng sẽ sao chép sâu hoặc sao chép nông?
Geek

9

Vì phiên bản 2.07 Kryo hỗ trợ nhân bản nông / sâu :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo rất nhanh, tại và trên trang của họ, bạn có thể tìm thấy một danh sách các công ty sử dụng nó trong sản xuất.


5

Sử dụng XStream toXML / fromXML trong bộ nhớ. Vô cùng nhanh chóng và đã có từ lâu và đang phát triển mạnh mẽ. Các đối tượng không cần phải tuần tự hóa và bạn không sử dụng sự phản chiếu (mặc dù XStream có). XStream có thể phân biệt các biến trỏ đến cùng một đối tượng và không vô tình tạo hai bản sao đầy đủ của thể hiện. Rất nhiều chi tiết như thế đã được rèn giũa trong những năm qua. Tôi đã sử dụng nó trong một số năm và nó là để đi. Nó dễ sử dụng như bạn có thể tưởng tượng.

new XStream().toXML(myObj)

hoặc là

new XStream().fromXML(myXML)

Nhân bản,

new XStream().fromXML(new XStream().toXML(myObj))

Ngắn gọn hơn:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));

3

Đối với các đối tượng phức tạp và khi hiệu suất không đáng kể, tôi sử dụng gson để tuần tự hóa đối tượng thành văn bản json, sau đó giải tuần tự hóa văn bản để có được đối tượng mới.

gson dựa trên sự phản chiếu sẽ hoạt động trong hầu hết các trường hợp, ngoại trừ transientcác trường sẽ không được sao chép và các đối tượng có tham chiếu tròn có nguyên nhân StackOverflowError.

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}

2

Phụ thuộc.

Đối với tốc độ, sử dụng DIY. Để chống đạn, sử dụng phản xạ.

BTW, tuần tự hóa không giống như phản xạ, vì một số đối tượng có thể cung cấp các phương thức tuần tự hóa bị ghi đè (readObject / writeObject) và chúng có thể bị lỗi


1
Sự phản chiếu không phải là bằng chứng đạn: nó có thể dẫn đến một số trường hợp trong đó đối tượng nhân bản của bạn có tham chiếu đến nguồn của bạn ... Nếu nguồn thay đổi, bản sao cũng sẽ thay đổi!
Guillaume

1

Tôi muốn giới thiệu cách DIY, kết hợp với phương thức hashCode () và equals () tốt sẽ dễ dàng chứng minh trong một bài kiểm tra đơn vị.


tốt, tôi lười biếng rất nhiều khi tạo ra một mã giả như vậy. Nhưng nó trông giống như con đường khôn ngoan hơn ...
Guillaume

2
xin lỗi, nhưng DIY là cách để CHỈ đi nếu không có giải pháp nào khác phù hợp với bạn..những điều gần như không bao giờ
Bozho

1

Tôi khuyên bạn nên ghi đè Object.clone (), gọi super.clone () trước và gọi ref = ref.clone () trên tất cả các tham chiếu mà bạn muốn sao chép sâu. Đó là ít nhiều Hãy tự mình tiếp cận nhưng cần mã hóa ít hơn một chút.


2
Đó là một trong nhiều vấn đề của phương thức nhân bản (bị hỏng): Trong hệ thống phân cấp lớp, bạn luôn phải gọi super.clone (), có thể dễ dàng bị lãng quên, đó là lý do tại sao tôi thích sử dụng một hàm tạo sao chép.
helpermethod

0

Để nhân bản sâu, triển khai tuần tự hóa trên mỗi lớp bạn muốn sao chép như thế này

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

Và sau đó sử dụng chức năng này:

public static Object deepClone(Object object) {
    try {
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

như thế này: Obj newObject = (Obj)deepClone(oldObject);

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.