Làm thế nào để bạn tạo một bản sao sâu của một đối tượng?


301

Hơi khó thực hiện một chức năng sao chép đối tượng sâu. Những bước bạn thực hiện để đảm bảo đối tượng ban đầu và nhân bản vô tính chia sẻ không có tài liệu tham khảo?


4
Kryo đã tích hợp hỗ trợ sao chép / nhân bản . Đây là sao chép trực tiếp từ đối tượng sang đối tượng, không phải đối tượng-> byte-> đối tượng.
NateS

1
Đây là một câu hỏi liên quan đã được hỏi sau: Giới thiệu tiện ích nhân bản sâu
Brad Cupit

Sử dụng thư viện nhân bản đã tiết kiệm trong ngày cho tôi! github.com/kostaskougios/claming
gaurav

Câu trả lời:


168

Một cách an toàn là tuần tự hóa đối tượng, sau đó giải tuần tự hóa. Điều này đảm bảo mọi thứ là một tài liệu tham khảo hoàn toàn mới.

Đây là một bài viết về cách làm điều này một cách hiệu quả.

Hãy cẩn thận: Các lớp có thể ghi đè tuần tự hóa sao cho các thể hiện mới không được tạo, ví dụ cho các singletons. Ngoài ra, điều này tất nhiên không hoạt động nếu các lớp của bạn không nối tiếp.


6
Xin lưu ý rằng việc triển khai FastByteArrayOutputStream được cung cấp trong bài viết có thể hiệu quả hơn. Nó sử dụng một mở rộng kiểu ArrayList khi bộ đệm đầy, nhưng tốt hơn là sử dụng cách tiếp cận mở rộng theo kiểu LinkedList. Thay vì tạo một bộ đệm 2x mới và ghi nhớ bộ đệm hiện tại, hãy duy trì một danh sách các bộ đệm được liên kết, thêm một bộ đệm mới khi hiện tại đầy. Nếu bạn nhận được yêu cầu ghi nhiều dữ liệu hơn mức vừa với kích thước bộ đệm mặc định của mình, hãy tạo một nút đệm chính xác như yêu cầu; các nút không cần phải có cùng kích thước.
Brian Harris


Một bài viết hay, giải thích về bản sao sâu thông qua việc
Quảng cáo Infinitum

Danh sách liên kết @BrianHarris không hiệu quả hơn mảng động. Chèn các phần tử vào một mảng động là độ phức tạp không đổi được khấu hao, trong khi chèn vào danh sách được liên kết là độ phức tạp tuyến tính
Norill Tempest

Làm thế nào nhiều tuần tự hóa và deserialize chậm hơn so với cách tiếp cận xây dựng sao chép?
Woland

75

Một vài người đã đề cập đến việc sử dụng hoặc ghi đè Object.clone(). Đừng làm điều đó. Object.clone()có một số vấn đề lớn, và việc sử dụng nó không được khuyến khích trong hầu hết các trường hợp. Vui lòng xem Mục 11, từ " Java hiệu quả " của Joshua Bloch để có câu trả lời đầy đủ. Tôi tin rằng bạn có thể sử dụng một cách an toàn Object.clone()trên các mảng kiểu nguyên thủy, nhưng ngoài ra, bạn cần thận trọng về việc sử dụng đúng cách và ghi đè lên bản sao.

Các lược đồ dựa trên tuần tự hóa (XML hoặc cách khác) là không rõ ràng.

Không có câu trả lời dễ dàng ở đây. Nếu bạn muốn sao chép sâu một đối tượng, bạn sẽ phải duyệt qua biểu đồ đối tượng và sao chép rõ ràng từng đối tượng con thông qua hàm tạo sao chép của đối tượng hoặc một phương thức nhà máy tĩnh để sao chép sâu đối tượng con. Bất biến (ví dụ Strings) không cần phải sao chép. Như một bên, bạn nên ủng hộ sự bất biến vì lý do này.


58

Bạn có thể tạo một bản sao sâu với tuần tự hóa mà không cần tạo tập tin.

Đối tượng của bạn mà bạn muốn sao chép sâu sẽ cần implement serializable. Nếu lớp không phải là cuối cùng hoặc không thể sửa đổi, hãy mở rộng lớp và thực hiện tuần tự hóa.

Chuyển đổi lớp của bạn thành một luồng byte:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

Khôi phục lớp của bạn từ một luồng byte:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();

4
Nếu lớp học là cuối cùng, làm thế nào bạn sẽ mở rộng nó?
Kumar Manish

1
Lớp @KumarM Biến MyContainer thực hiện Nối tiếp {Ví dụ MyFinalClass; ...}
Matteo T.

Tôi thấy đây là một câu trả lời tuyệt vời. Clone là một mớ hỗn độn
blackbird014

@MatteoT. làm thế nào thuộc tính lớp không tuần tự hóa sẽ được tuần tự hóa, không tuần tự hóa instancetrong trường hợp này?
Far

40

Bạn có thể thực hiện một bản sao sâu dựa trên tuần tự hóa bằng cách sử dụng org.apache.commons.lang3.SerializationUtils.clone(T)trong Apache Commons Lang, nhưng hãy cẩn thận, hiệu suất của nó rất tệ.

Nói chung, cách tốt nhất là viết các phương thức nhân bản của riêng bạn cho mỗi lớp của một đối tượng trong biểu đồ đối tượng cần nhân bản.


Nó cũng có sẵn trongorg.apache.commons.lang.SerializationUtils
Pino

25

Một cách để thực hiện sao chép sâu là thêm các hàm tạo sao chép vào mỗi lớp liên kết. Một constructor sao chép lấy một thể hiện của 'this' làm đối số duy nhất của nó và sao chép tất cả các giá trị từ nó. Khá nhiều công việc, nhưng khá đơn giản và an toàn.

EDIT: lưu ý rằng bạn không cần sử dụng các phương thức truy cập để đọc các trường. Bạn có thể truy cập tất cả các trường trực tiếp vì thể hiện nguồn luôn cùng loại với thể hiện với hàm tạo sao chép. Rõ ràng nhưng có thể bị bỏ qua.

Thí dụ:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Chỉnh sửa: Lưu ý rằng khi sử dụng các hàm tạo sao chép, bạn cần biết loại thời gian chạy của đối tượng bạn đang sao chép. Với cách tiếp cận trên, bạn không thể dễ dàng sao chép một danh sách hỗn hợp (bạn có thể làm điều đó với một số mã phản chiếu).


1
Chỉ quan tâm đến trường hợp những gì bạn đang sao chép là một lớp con, nhưng đang được cha mẹ tham chiếu. Có thể ghi đè lên constructor sao chép?
Thịt lợn 'n' Bunny

Tại sao lớp cha của bạn đề cập đến lớp con của nó? Bạn có thể đưa ra một ví dụ không?
Adriaan Koster

1
lớp công cộng Xe kéo dài Xe và sau đó gọi xe là phương tiện. originaList = new ArrayList <Xe>; copyList = new ArrayList <Xe>; originalList.add (Xe mới ()); for (Phương tiện xe cộ: phương tiện liệt kê) {copyList.add (phương tiện mới (phương tiện)); }
Thịt lợn 'n' Bunny

@AdriaanKoster: Nếu danh sách ban đầu chứa a Toyota, mã của bạn sẽ đặt một Cartrong danh sách đích. Nhân bản chính xác thường yêu cầu lớp cung cấp một phương thức nhà máy ảo có hợp đồng tuyên bố rằng nó sẽ trả về một đối tượng mới của lớp của chính nó; bản thân công cụ sao chép phải protectedđảm bảo rằng nó sẽ chỉ được sử dụng để xây dựng các đối tượng có loại chính xác khớp với đối tượng được sao chép).
supercat

Vì vậy, nếu tôi hiểu chính xác đề xuất của bạn, phương thức xuất xưởng sẽ gọi hàm tạo sao chép riêng? Làm thế nào các hàm tạo sao chép của một lớp con đảm bảo các trường siêu lớp được khởi tạo? Bạn có thể đưa ra một ví dụ không?
Adriaan Koster

20

Bạn có thể sử dụng một thư viện có API đơn giản và thực hiện nhân bản tương đối nhanh với sự phản chiếu (nên nhanh hơn các phương thức tuần tự hóa).

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

19

Apache commons cung cấp một cách nhanh chóng để sao chép sâu một đối tượng.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);

1
Điều này chỉ hoạt động cho đối tượng thực hiện Nối tiếp và cũng cho tất cả các trường trong đó thực hiện Nối tiếp mặc dù.
wonhee

11

XStream thực sự hữu ích trong những trường hợp như vậy. Đây là một mã đơn giản để làm nhân bản

private static final XStream XSTREAM = new XStream();
...

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));

1
Không, bạn không cần chi phí của xml-ing đối tượng.
egelev

@egeleve Bạn có nhận ra rằng bạn đang trả lời một nhận xét từ '08 phải không? Tôi không sử dụng Java nữa và có thể có các công cụ tốt hơn bây giờ. Tuy nhiên, tại thời điểm đó, việc tuần tự hóa sang một định dạng khác và sau đó tuần tự hóa trở lại có vẻ như là một hack tốt - nó chắc chắn không hiệu quả.
sankara


10

Đối với người dùng Spring Framework . Sử dụng lớp org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}

9

Đố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 thư viện json, như 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 để lấy đố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 <T> T copy(T anObject, Class<T> classInfo) {
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(anObject);
    T newObject = gson.fromJson(text, classInfo);
    return newObject;
}
public static void main(String[] args) {
    String originalObject = "hello";
    String copiedObject = copy(originalObject, String.class);
}

3
Vui lòng tuân thủ các quy ước đặt tên Java cho riêng bạn và của chúng tôi.
Patrick Bergner

8

Sử dụng XStream ( http://x-stream.github.io/ ). Bạn thậm chí có thể kiểm soát các thuộc tính nào bạn có thể bỏ qua thông qua các chú thích hoặc chỉ định rõ ràng tên thuộc tính cho lớp XStream. Ngoài ra, bạn không cần phải thực hiện giao diện clonable.


7

Sao chép sâu chỉ có thể được thực hiện với sự đồng ý của mỗi lớp. Nếu bạn có quyền kiểm soát hệ thống phân cấp lớp thì bạn có thể thực hiện giao diện có thể sao chép và thực hiện phương thức Clone. Mặt khác, thực hiện một bản sao sâu là không thể thực hiện một cách an toàn vì đối tượng cũng có thể đang chia sẻ tài nguyên phi dữ liệu (ví dụ: kết nối cơ sở dữ liệu). Tuy nhiên, nói chung, sao chép sâu được coi là thực hành xấu trong môi trường Java và nên tránh thông qua các thực tiễn thiết kế phù hợp.


2
Bạn có thể mô tả "thực hành thiết kế phù hợp"?
fklappan

6
import com.thoughtworks.xstream.XStream;

public class deepCopy {
    private static  XStream xstream = new XStream();

    //serialize with Xstream them deserialize ...
    public static Object deepCopy(Object obj){
        return xstream.fromXML(xstream.toXML(obj));
    }
}

5

Tôi đã sử dụng Dozer để nhân bản các đối tượng java và thật tuyệt vời, thư viện Kryo là một lựa chọn tuyệt vời khác.



2

1)

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;
   }
 }

2)

    // (1) create a MyPerson object named Al
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
    MyPerson al = new MyPerson("Al", "Arun", address);

    // (2) make a deep clone of Al
    MyPerson neighbor = (MyPerson)deepClone(al);

Ở đây lớp MyPerson và MyAddress của bạn phải thực hiện giao diện có thể điều chỉnh được


2

Sử dụng Jackson để tuần tự hóa và giải tuần tự hóa đối tượng. Việc thực hiện này không yêu cầu đối tượng thực hiện lớp Nối tiếp.

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

  }
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.