实现深层对象复制功能有点困难。您采取什么步骤来确保原始对象和克隆对象没有引用?
实现深层对象复制功能有点困难。您采取什么步骤来确保原始对象和克隆对象没有引用?
Answers:
一种安全的方法是序列化对象,然后反序列化。这样可以确保所有内容都是全新的参考。
注意事项:类可能会覆盖序列化,这样就不会创建新实例,例如单例。如果您的类不是可序列化的,那么这当然也行不通。
少数人提到使用或覆盖Object.clone()
。不要这样 Object.clone()
有一些主要问题,在大多数情况下不建议使用。请参阅Joshua Bloch撰写的“ Effective Java ”中的第11项,以获取完整的答案。我相信您可以安全地Object.clone()
在原始类型数组上使用,但除此之外,您还需要谨慎地正确使用和覆盖克隆。
依赖序列化的方案(XML或其他)是不可靠的。
这里没有简单的答案。如果要深层复制对象,则必须遍历对象图并通过对象的复制构造函数或静态工厂方法显式复制每个子对象,而该方法又会深层复制子对象。不变变量(例如String
s)不需要复制。顺便说一句,出于这个原因,您应该支持不变性。
您可以通过序列化制作深层副本,而无需创建文件。
您想要深层复制的对象将需要implement serializable
。如果该类不是最终的或无法修改,请扩展该类并实现可序列化。
将您的类转换为字节流:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();
从字节流中还原类:
ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
instance
在这种情况下,如何将不可序列化的类属性进行序列化和不可序列化?
您可以org.apache.commons.lang3.SerializationUtils.clone(T)
在Apache Commons Lang中使用基于序列化的深层克隆,但要小心-性能太差了。
通常,最佳实践是为需要克隆的对象图中的对象的每个类编写自己的克隆方法。
org.apache.commons.lang.SerializationUtils
实现深层复制的一种方法是将复制构造函数添加到每个关联的类。复制构造函数将'this'的实例作为其单个参数,并从中复制所有值。很多工作,但是非常简单和安全。
编辑:请注意,您不需要使用访问器方法来读取字段。您可以直接访问所有字段,因为源实例始终与复制构造函数的实例具有相同的类型。显而易见,但可能会被忽略。
例:
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;
}
}
编辑:请注意,在使用复制构造函数时,您需要了解要复制的对象的运行时类型。使用上述方法,您无法轻松地复制混合列表(您可以使用一些反射代码来完成此操作)。
Toyota
,则您的代码会将a Car
放入目标列表。正确的克隆通常要求类提供一个虚拟工厂方法,该方法的合同规定它将返回其自己类的新对象。复制构造器本身应protected
确保仅用于构造其精确类型与被复制对象的类型精确匹配的对象。
XStream在这种情况下确实很有用。这是执行克隆的简单代码
private static final XStream XSTREAM = new XStream();
...
Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
对于Spring Framework用户。使用类org.springframework.util.SerializationUtils
:
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
对于复杂的对象,当性能不重要时,我使用json库,例如gson 将对象序列化为json文本,然后反序列化文本以获取新对象。
在大多数情况下,基于反射的gson都可以使用,除了transient
不会复制字段和带有cause的循环引用的对象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);
}
使用XStream(http://x-stream.github.io/)。您甚至可以控制您可以通过注释或为XStream类显式指定属性名称来忽略哪些属性。而且,您不需要实现可克隆的接口。
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);
在这里,您的MyPerson和MyAddress类必须实现Serilazable接口