it-swarm-vi.tech

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

Trong Java, 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?

273
Andrei Savu

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 được không được tạo, ví dụ: cho người độc thân. 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.

160
Jason Cohen

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 Object.clone() một cách an toàn 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 và ghi đè đúng cách.

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 lần lượt sao chép sâu đối tượng con. Không thể thay đổi (ví dụ: Strings) không cần sao chép. Như một bên, bạn nên ủng hộ sự bất biến vì lý do này.

67
Julien Chastang

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 đế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();
53
Thargor

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.

36
user8690

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. Hàm tạo 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).

23
Adriaan Koster

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);
18
TheByeByeMan

Bạn có thể sử dụng thư viện có API đơn giản và thực hiện nhân bản tương đối nhanh với phản xạ (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
17
CorayThan

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));
11
sankara

Một cách tiếp cận rất dễ dàng và đơn giản là sử dụng Jackson JSON để tuần tự hóa Đối tượng Java phức tạp thành JSON và đọc lại.

http://wiki.fasterxml.com/JacksonInFiveMinutes

9
Ravi Chinoy

Dành cho Spring Framework người dùng. 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));
}
8
Igor Rybak

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.

8
Adi

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, việc sao chép 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.

7
Orion Adrian

Đố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ừ trường transient sẽ không được sao chép và các đối tượng có tham chiếu vòng tròn với 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);
}
6
tiboo
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));
    }
}
6
user946968

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

4
supernova

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
Arun

BeanUtils thực hiện công việc nhân bản sâu thực sự tốt.

BeanUtils.cloneBean(obj);
1
Alfergon