339

实现深度对象复制功能有点困难。您采取了哪些步骤来确保原始对象和克隆对象不共享引用?

4

20 回答 20

182

一种安全的方法是序列化对象,然后反序列化。这确保了一切都是全新的参考。

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

警告:类可能会覆盖序列化,从而不会创建新实例,例如单例。如果您的类不可序列化,这当然也不起作用。

于 2008-09-15T15:42:23.633 回答
81

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

依赖序列化(XML 或其他)的方案很笨拙。

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

于 2008-12-09T22:38:22.177 回答
62

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

您希望深​​度复制的对象将需要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 = new ObjectInputStream(bais).readObject();
于 2011-09-29T11:17:53.417 回答
45

您可以使用org.apache.commons.lang3.SerializationUtils.clone(T)Apache Commons Lang 进行基于序列化的深度克隆,但要小心——性能非常糟糕。

通常,最佳实践是为需要克隆的对象图中的每个对象类编写自己的克隆方法。

于 2008-09-15T16:48:58.060 回答
29

实现深度复制的一种方法是将复制构造函数添加到每个关联的类。复制构造函数将 '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 实例,除非您明确解决此问题。您可以使用反射在参数的运行时类型中查找复制构造函数。但如果需要以一般方式涵盖继承,我建议不要走这条路并寻找另一种解决方案。

于 2011-09-29T11:57:14.180 回答
22

您可以使用具有简单 API 的库,并通过反射执行相对快速的克隆(应该比序列化方法更快)。

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
于 2014-03-20T22:52:09.387 回答
19

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

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
于 2015-01-28T14:53:24.170 回答
17

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

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
于 2017-07-19T22:15:18.957 回答
12

对于复杂的对象并且当性能不重要时,我使用 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);
}
于 2016-11-29T07:24:14.260 回答
11

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

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

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
于 2008-10-23T08:03:08.533 回答
10

一种非常简单的方法是使用 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();
于 2012-02-07T03:51:31.897 回答
8

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

于 2008-09-16T05:08:35.693 回答
7

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

于 2008-09-15T15:44:32.333 回答
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));
    }
}
于 2011-09-15T13:55:43.983 回答
6

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

  }
于 2019-11-21T04:43:36.647 回答
5

我使用Dozer来克隆 java 对象,它很棒,Kryo库是另一个很好的选择。

于 2014-10-17T02:08:59.940 回答
2

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

BeanUtils.cloneBean(obj);
于 2016-02-16T10:24:08.420 回答
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);

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

于 2016-11-11T14:51:53.347 回答
1

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

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);
    }
}
于 2021-01-05T18:58:52.357 回答
0

这是一个关于如何深度克隆任何对象的简单示例:先实现可序列化

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

是你如何获得克隆。

于 2020-08-28T20:00:53.747 回答