如何制作对象的深层副本?


301

实现深层对象复制功能有点困难。您采取什么步骤来确保原始对象和克隆对象没有引用?


4
Kryo具有对复制/克隆的内置支持。这是直接从对象复制到对象,而不是对象->字节->对象。
NateS 2012年

1
这是一个相关的问题,稍后会被问到:深度克隆实用程序推荐
Brad Cupit

使用克隆库为我节省了一天的时间! github.com/kostaskougios/cloning
gaurav

Answers:


168

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

这是有关如何有效执行此操作的文章

注意事项:类可能会覆盖序列化,这样就不会创建新实例,例如单例。如果您的类不是可序列化的,那么这当然也行不通。


6
请注意,本文中提供的FastByteArrayOutputStream实现可能更有效。当缓冲区填满时,它使用ArrayList样式的扩展,但是最好使用LinkedList样式的扩展方法。与其创建一个新的2x缓冲区并覆盖当前缓冲区,不如保留一个缓冲区的链表,在当前缓冲区填满时添加一个新的缓冲区。如果请求写入的数据超出了默认缓冲区大小所能容纳的范围,请创建一个与该请求一样大的缓冲区节点;节点的大小不必相同。
布赖恩·哈里斯


一篇好文章,这说明通过序列化的深层副本:javaworld.com/article/2077578/learn-java/...
循环往复

@BrianHarris链表并不比动态数组更有效。将元素插入动态数组将摊销恒定的复杂度,而将链接列表插入将具有线性复杂度
Norill Tempest

序列化和反序列化比复制构造方法慢多少?
Woland

75

少数人提到使用或覆盖Object.clone()。不要这样 Object.clone()有一些主要问题,在大多数情况下不建议使用。请参阅Joshua Bloch撰写的“ Effective Java ”中的第11项,以获取完整的答案。我相信您可以安全地Object.clone()在原始类型数组上使用,但除此之外,您还需要谨慎地正确使用和覆盖克隆。

依赖序列化的方案(XML或其他)是不可靠的。

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


58

您可以通过序列化制作深层副本,而无需创建文件。

您想要深层复制的对象将需要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();

4
如果课程是最后的课程,您将如何扩展它?
Kumar Manish

1
@KumarManish类MyContainer实现了Serializable {MyFinalClass实例;...}
Matteo T.

我发现这是一个很好的答复。克隆是一团糟
blackbird014

@MatteoT。instance在这种情况下,如何将不可序列化的类属性进行序列化和不可序列化?
Farid


25

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

编辑:请注意,在使用复制构造函数时,您需要了解要复制的对象的运行时类型。使用上述方法,您无法轻松地复制混合列表(您可以使用一些反射代码来完成此操作)。


1
仅在您要复制的内容是子类但被父对象引用的情况下感兴趣。是否可以覆盖副本构造函数?
猪肉兔宝宝(2013年

为什么您的父类引用其子类?你能给个例子吗?
Adriaan Koster,

1
公共类Car扩展了Vehicle,然后将汽车称为车辆。originaList =新的ArrayList <Vehicle>; copyList =新的ArrayList <Vehicle>; originalList.add(new Car()); 对于(Vehicle vehicle:vehicleList){copyList.add(new Vehicle(vehicle)); }
Pork'n'Bunny

@AdriaanKoster:如果原始列表包含Toyota,则您的代码会将a Car放入目标列表。正确的克隆通常要求类提供一个虚拟工厂方法,该方法的合同规定它将返回其自己类的新对象。复制构造器本身应protected确保仅用于构造其精确类型与被复制对象的类型精确匹配的对象。
2013年

因此,如果我正确理解了您的建议,那么工厂方法将调用私有副本构造函数?子类的副本构造函数如何确保超类字段已初始化?你能给个例子吗?
Adriaan Koster

20

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

Cloner cloner = new Cloner();

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

19

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

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

1
不过,这仅适用于实现Serializable的对象,也适用于其中实现了Serializable的所有字段。
wonhee

11

XStream在这种情况下确实很有用。这是执行克隆的简单代码

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

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

1
不,您不需要对对象进行xml处理的开销。
egelev

@egeleve您确实意识到您正在回复'08的评论,对吗?我不再使用Java,现在可能会有更好的工具。但是在那时,先序列化为另一种格式,然后再进行序列化,这似乎是一个不错的技巧-绝对效率不高。
sankara,2015年


10

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

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

9

对于复杂的对象,当性能不重要时,我使用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);
}

3
为了您自己和我们的缘故,请遵守Java命名约定。
Patrick Bergner

8

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


7

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


2
您能否描述“适当的设计实践”?
fklappan

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



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类必须实现Serilazable接口


2

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

  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.