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?
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?
Câu trả lời:
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.
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ụ String
s) 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.
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();
instance
trong trường hợp này?
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.
org.apache.commons.lang.SerializationUtils
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).
Toyota
, mã của bạn sẽ đặt một Car
trong 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).
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
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);
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));
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.
Đố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));
}
Đố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ừ transient
cá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);
}
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.
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.
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));
}
}
BeanUtils thực hiện rất tốt công việc nhân bản sâu.
BeanUtils.cloneBean(obj);
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
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);
}