如何对对象进行深度复制?

实现深度对象复制函数有点困难。您采取什么步骤来确保原始对象和克隆对象没有共享引用?

414751 次浏览

一个安全的方法是序列化对象,然后反序列化。这确保了所有内容都是全新的参考。

这是一篇文章关于如何有效地做到这一点。

注意:类有可能重写序列化,从而创建新实例,例如单例。当然,如果你的类不是序列化的,这也行不通。

深度复制只能在每个班级同意的情况下进行。如果您可以控制类层次结构,那么您可以实现可克隆接口并实现Clone方法。否则进行深度复制是不可能安全的,因为对象也可能共享非数据资源(例如数据库连接)。一般来说,深度复制在Java环境中被认为是不好的做法,应该通过适当的设计实践来避免。

你可以在Apache Commons Lang中使用org.apache.commons.lang3.SerializationUtils.clone(T)来做一个基于序列化的深度克隆,但是要小心——性能非常糟糕。

一般来说,最好的做法是为需要克隆的对象图中对象的每个类编写自己的克隆方法。

使用XStream (http://x-stream.github.io/)。您甚至可以通过注释或显式地指定XStream类的属性名来控制可以忽略哪些属性。此外,您不需要实现可克隆的接口。

XStream在这种情况下非常有用。这是一个简单的代码来做克隆

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


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

一些人提到了使用或覆盖Object.clone()。不要这样做。Object.clone()有一些主要的问题,在大多数情况下不鼓励使用它。请参阅第11项,来自Joshua Bloch的“有效的Java”以获得完整的答案。我相信你可以安全地在基本类型数组上使用Object.clone(),但除此之外,你需要明智地正确使用和重写clone。

依赖于序列化(XML或其他方式)的方案是拙劣的。

这个问题没有简单的答案。如果你想要深度复制一个对象,你必须遍历对象图,并通过对象的复制构造函数或静态工厂方法显式复制每个子对象,然后再深度复制子对象。不需要复制不可变对象(例如Strings)。顺便说一句,出于这个原因,您应该支持不可变性。

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

您可以使用序列化进行深度复制,而无需创建文件。

你想要深度复制的对象需要implement serializable。如果类不是final或不能修改,则扩展类并实现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 = new ObjectInputStream(bais).readObject();

实现深度复制的一种方法是向每个相关类添加复制构造函数。复制构造函数将'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;
}
}

编辑:注意,复制构造函数不考虑继承。例如:如果您将OnlineOrder (Order的子类)传递给一个复制构造函数,则将在副本中创建一个常规的Order实例,除非您显式地解决这个问题。可以使用反射在实参的运行时类型中查找复制构造函数。但是我建议不要走这条路,如果需要以一般的方式讨论继承,就寻找另一种解决方案。

一种非常简单的方法是使用Jackson JSON将复杂的Java对象序列化为JSON并读取回来。

来自https://github.com/FasterXML/jackson-databind/#5-minute-tutorial-streaming-parser-generator:

JsonFactory f = mapper.getFactory(); // may alternatively construct directly too


// First: write simple JSON output
File jsonFile = new File("test.json");
JsonGenerator g = f.createGenerator(jsonFile);
// write JSON: { "message" : "Hello world!" }
g.writeStartObject();
g.writeStringField("message", "Hello world!");
g.writeEndObject();
g.close();


// Second: read file back
JsonParser p = f.createParser(jsonFile);


JsonToken t = p.nextToken(); // Should be JsonToken.START_OBJECT
t = p.nextToken(); // JsonToken.FIELD_NAME
if ((t != JsonToken.FIELD_NAME) || !"message".equals(p.getCurrentName())) {
// handle error
}
t = p.nextToken();
if (t != JsonToken.VALUE_STRING) {
// similarly
}
String msg = p.getText();
System.out.printf("My message to you is: %s!\n", msg);
p.close();

你可以利用图书馆,它有一个简单的API,并使用反射执行相对快速的克隆(应该比序列化方法快)。

Cloner cloner = new Cloner();


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

我使用推土机来克隆java对象,它在这方面很棒,Kryo库是另一个很好的选择。

Apache commons提供了一种快速的深度克隆对象的方法。

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

BeanUtils在深度克隆bean方面做得非常好。

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

这里,您的MyPerson和MyAddress类必须实现可序列化接口

对于复杂的对象,当性能不重要时,我使用json库,如gson 将对象序列化为json文本,然后反序列化文本以获得新对象

基于反射的gson在大多数情况下都是有效的,除非transient字段将不会被复制,并且带有原因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);
}

对于Spring框架用户。使用类org.springframework.util.SerializationUtils:

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

使用Jackson序列化和反序列化对象。此实现不需要对象实现Serializable类。

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


}
这是一个关于如何深度克隆任何对象的简单示例: 首先实现serializable

public class CSVTable implements Serializable{
Table<Integer, Integer, String> table;
public CSVTable() {
this.table = HashBasedTable.create();
}
    

public CSVTable deepClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);


ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (CSVTable) ois.readObject();
} catch (IOException e) {
return null;
} catch (ClassNotFoundException e) {
return null;
}
}


}

然后

CSVTable table = new CSVTable();
CSVTable tempTable = table.deepClone();

才能得到克隆体。

下面是一种通用的深度克隆方法,使用字节数组流进行对象序列化和反序列化(以避免写入文件)。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepClone(T t) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);) {
oos.writeObject(t);
byte[] bytes = baos.toByteArray();
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

一个非常快速和简单的一行程序解决方案是使用Jackson。

看一下示例代码片段:

ObjectMapper objectMapper = new ObjectMapper();


MyClass deepCopyObject = objectMapper
.readValue(objectMapper.writeValueAsString(originalObject), MyClass.class);

在上面的例子中:"MyClass"引用要复制的对象的类。