如何在Java中创建对象的深层副本?

时间:2008-09-15 15:39:04

标签: java class clone

在java中,实现深层对象复制功能有点困难。您采取了哪些步骤来确保原始对象和克隆的对象没有共享?

20 个答案:

答案 0 :(得分:159)

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

Here's an article关于如何有效地做到这一点。

注意事项:类可以覆盖序列化,以便新的实例创建,例如对于单身人士。如果您的类不是Serializable,那么这当然不起作用。

答案 1 :(得分:67)

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

依赖于序列化(XML或其他)的方案很糟糕。

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

答案 2 :(得分:53)

您可以在不创建文件的情况下使用序列化进行深层复制。

您希望深层复制的对象需要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 = (Object) new ObjectInputStream(bais).readObject();

答案 3 :(得分:36)

你可以在Apache Commons Lang中使用org.apache.commons.lang3.SerializationUtils.clone(T)进行基于序列化的深度克隆,但要小心 - 性能很差。

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

答案 4 :(得分:23)

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

编辑:请注意,使用复制构造函数时,您需要知道要复制的对象的运行时类型。使用上述方法,您无法轻松复制混合列表(您可以使用某些反射代码进行复制)。

答案 5 :(得分:18)

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

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

答案 6 :(得分:17)

你可以use a library拥有一个简单的API,并使用反射执行相对快速的克隆(应该比序列化方法更快)。

Cloner cloner = new Cloner();

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

答案 7 :(得分:11)

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

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

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

答案 8 :(得分:9)

一种非常简单易用的方法是使用Jackson JSON将复杂的Java Object序列化为JSON并将其读回。

http://wiki.fasterxml.com/JacksonInFiveMinutes

答案 9 :(得分:8)

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

答案 10 :(得分:8)

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

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

答案 11 :(得分:7)

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

答案 12 :(得分:6)

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

答案 13 :(得分: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));
    }
}

答案 14 :(得分:4)

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

答案 15 :(得分: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类必须实现可串行接口

答案 16 :(得分:1)

BeanUtils在深度克隆bean方面表现非常出色。

BeanUtils.cloneBean(obj);

答案 17 :(得分: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);
    }
}

答案 18 :(得分:0)

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

  }

答案 19 :(得分: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();

是您获得克隆的方式。