题 如何在Java中复制对象?


考虑以下代码:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

所以,我想要复制 dum 至 dumtwo 并改变 dum 不影响 dumtwo。但上面的代码并没有这样做。当我改变某些东西时 dum,同样的变化正在发生 dumtwo 也。

我猜,当我说 dumtwo = dum,Java复制 仅供参考。那么,有没有办法创建一个新的副本 dum 并将其分配给 dumtwo


653
2018-05-15 14:30


起源




答案:


创建一个复制构造函数:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

每个对象都有一个克隆方法,可用于复制对象,但不要使用它。创建一个类并执行不正确的克隆方法太容易了。如果你打算这样做,请至少阅读Joshua Bloch关于它的内容 有效的Java


527
2018-05-15 14:35



但是他必须将他的代码改为DummyBean two = new DummyBean(one);对? - Chris K
这种方法是否有效地完成了与深层复制相同的操作? - Matthew Piziak
@MatthewPiziak,对我来说 - 这不会是一个深度克隆,因为任何嵌套对象仍然会引用原始源实例,而不是重复,除非每个引用(非值类型)对象提供与上面相同的构造函数模板。 - SliverNinja - MSFT
@Timmmm:是的,他们会引用相同的String,但因为它是不可变的,所以没问题。原语也是如此。对于非基元,您只需递归复制构造函数调用。例如如果DummyBean引用了FooBar,那么FooBar应该有contructor FooBar(另一个是FooBar),而dummy应该调用this.foobar = new FooBar(another.foobar) - egaga
@ChristianVielma:不,它不会是“johndoe”。就像Timmmm所说,字符串本身是不可改变的。使用一个setDummy(..),您可以将引用设置为一个指向“johndoe”,但不能指向一个。 - keuleJ


基础: Java中的对象复制。

让我们假设一个对象 - obj1,包含两个对象, containedObj1 和 containedObj2
enter image description here

浅拷贝:
浅层复制创造了一个新的 instance 同一个类,并将所有字段复制到新实例并返回它。 对象类 提供了一个 clone 方法并为浅拷贝提供支持。
enter image description here

深拷贝:
当发生深度复制时 将对象与其引用的对象一起复制。下图显示 obj1 在对其执行了深层复制之后。 不仅如此 obj1 被复制了,但其中包含的对象也已被复制。我们可以用 Java Object Serialization 做一个深刻的副本。不幸的是,这种方法也存在一些问题(详细的例子)。
enter image description here

可能的问题:
clone 很难正确实施。
最好使用 防御性复制复制构造函数(如@egaga的回复)或 静态工厂方法

  1. 如果你有一个对象,你知道它有公共 clone() 方法,但你不知道编译时对象的类型,那么你有问题。 Java有一个名为的接口 Cloneable。实际上,如果我们想要创建一个对象,我们应该实现这个接口 CloneableObject.clone 是 保护所以我们必须 覆盖 它使用公共方法,以便可以访问它。
  2. 当我们尝试时会出现另一个问题 深刻复制 一个 复杂的对象。假设 clone() 所有成员对象变量的方法也做深度复制,这对假设来说太冒险了。您必须控制所有类中的代码。

例如 org.apache.commons.lang.SerializationUtils 将使用序列化的深度克隆方法(资源)。如果我们需要克隆Bean,那么有几种实用方法 org.apache.commons.beanutils (资源)。

  • cloneBean 将基于可用的属性getter和setter克隆bean,即使bean类本身没有实现Cloneable。
  • copyProperties对于属性名称相同的所有情况,将属性值从原始bean复制到目标bean。

352
2018-03-23 05:47



你能解释一下另一个包含的对象吗? - Freakyuser
@Chandra Sekhar“浅拷贝创建同一个类的新实例并将所有字段复制到新实例并返回它”提到所有字段是错误的,bcz对象不会被复制只有被复制的引用被指向旧的(原始)所指向的同一个对象。 - JAVA
@sunny - Chandra的描述是正确的。你对所发生的事情的描述也是如此;我说你对“复制所有领域”的含义有不正确的理解。场 是 参考,它不是被引用的对象。 “复制所有字段” 手段 “复制所有这些参考”。对于任何对你有相同错误解释的人来说,对于“复制所有字段”的陈述,你指出这究竟意味着什么是好的。 :) - ToolmakerSteve
...如果我们根据某些较低级别的OO语言来考虑对象的“指针”,那么这样的字段将包含存储器中的地址(例如“0x70FF1234”),在该地址处找到对象数据。该地址是正在复制(分配)的“字段值”。你是对的,最终结果是两个对象都有引用(指向)同一对象的字段。 - ToolmakerSteve


请按以下步骤操作:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

无论你想要获得另一个对象,只需执行克隆即可。 例如:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object

93
2018-05-17 09:27



你测试过这个吗?我可以将它用于我的项目,重要的是要正确。 - misty
@misty我已经测试过了。在我的制作应用程序上完美运行 - tieorange
克隆后,当您修改原始对象时,它也会修改克隆。 - Sibish
我也有同样的问题,当我更改原始对象的属性时,然后在克隆对象中更改值。 - jmoran
这是错误的 不 要求的深刻副本。 - Bluehorn


在包中 import org.apache.commons.lang.SerializationUtils; 有一种方法:

SerializationUtils.clone(Object);

例:

this.myObjectCloned = SerializationUtils.clone(this.object);

91
2018-05-02 19:45



只要对象实现 Serializable - Androiderson
在这种情况下,如果最后一个是静态的,则克隆对象没有对原始对象的引用。 - Dante
第三方库只是为了克隆对象! - Khan
@Khan,“只是为了第三方图书馆”是一个完全独立的讨论! :d - Charles Wood


为什么没有使用Reflection API的答案?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

这很简单。

编辑:通过递归包含子对象

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

34
2017-08-16 09:27



这看起来好多了,但是你只需要考虑最后的字段,因为setAccessible(true)可能会失败,所以也许你需要单独处理在分别调用field.set(clone,field.get(obj))时抛出的异常IllegalAccessException。 - Max
我非常喜欢它,但你可以重构它来使用泛型吗? private static <T> T cloneObject(T obj){....} - Adelin
我认为当我们从属性引用父母时会出现问题: Class A { B child; } Class B{ A parent; } - nhthai
有时一次投票是不够的。 - Display Name
这很容易出错。不知道它将如何处理集合 - ACV


我使用Google的JSON库对其进行序列化,然后创建序列化对象的新实例。它通过一些限制进行深度复制:

  • 不能有任何递归引用

  • 它不会复制不同类型的数组

  • 应该键入数组和列表,否则它将找不到要实例化的类

  • 您可能需要在自己声明的类中封装字符串

我还使用这个类来保存用户首选项,窗口以及在运行时重新加载的内容。它非常易于使用和有效。

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}

22
2017-07-09 20:14





是的,您只是在引用该对象。如果对象实现,您可以克隆该对象 Cloneable

查看这篇关于复制对象的wiki文章。

请参阅此处:对象复制


21
2018-05-15 14:36