关于课程:如何在Java中制作对象的深层副本?

关于课程:如何在Java中制作对象的深层副本?

How do you make a deep copy of an object in Java?

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


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

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

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


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

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

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


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

您要深度复制的对象需要为implement serializable。如果该类不是最终的或无法修改,请扩展该类并实现可序列化。

将您的类转换为字节流:

1
2
3
4
5
6
7
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

从字节流中还原类:

1
2
ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();

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

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


实现深层复制的一种方法是将复制构造函数添加到每个关联的类。复制构造函数将'this'的实例作为其单个参数,并从中复制所有值。很多工作,但是非常简单和安全。

编辑:请注意,您不需要使用访问器方法来读取字段。您可以直接访问所有字段,因为源实例的类型始终与使用复制构造函数的实例相同。显而易见,但可能会被忽略。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
    }
}

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


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

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


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

1
2
3
4
Cloner cloner = new Cloner();

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

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

1
2
3
4
private static final XStream XSTREAM = new XStream();
...

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

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

http://wiki.fasterxml.com/JacksonInFiveMinutes


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

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

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


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

在大多数情况下,基于反射的gson将起作用,除了transient字段将不会被复制,并且循环引用的对象的原因为StackOverflowError

1
2
3
4
5
6
7
8
9
10
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);
}

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


1
2
3
4
5
6
7
8
9
10
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));
    }
}

我使用Dozer克隆Java对象,这很不错,Kryo库是另一个很好的选择。


1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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接口


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

1
BeanUtils.cloneBean(obj);

推荐阅读