如何在Java中复制对象?


794

考虑下面的代码:

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'

因此,我想复制dumdumtwo并且更改dum而不影响dumtwo。但是上面的代码没有这样做。当我更改时dum,也会发生相同的更改dumtwo

我想,当我说时dumtwo = dum,Java 复制参考。那么,有什么方法可以创建的新副本dum并将其分配给dumtwo

Answers:


611

创建一个副本构造函数:

class DummyBean {
  private String dummy;

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

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


45
但是随后他不得不将其代码更改为DummyBean 2 = new DummyBean(one); 对?
克里斯(Chris K)2010年

12
这种方法是否可以有效地完成与深层复制相同的任务?
马修·皮兹亚克

124
@MatthewPiziak,对我来说,这不是一个深度克隆,因为任何嵌套对象仍将引用原始源实例,而不是重复对象,除非每个引用(非值类型)对象都提供与上述相同的构造函数模板。
SliverNinja-MSFT

17
@Timmmm:是的,他们将引用相同的String,但是因为它是不可变的,所以可以。原语也是如此。对于非基本类型,您只需递归复制构造函数调用。例如,如果DummyBean引用了FooBar,则FooBar应该具有构造函数FooBar(FooBar another),并且虚拟对象应调用this.foobar = new FooBar(another.foobar)
egaga 2012年

7
@ChristianVielma:不,不会是“ johndoe”。就像Timmmm所说的那样,字符串本身是不可变的。使用setDummy(..),您可以将引用一一设置为指向“ johndoe”,而不是一一对应。
keuleJ 2012年

404

基本: Java中的对象复制。

让我们假设一个对象- obj1,它包含两个对象,containedObj1containedObj2
在此处输入图片说明

浅表复制:
浅表复制创建instance相同类的新表,并将所有字段复制到新实例并返回它。对象类提供了一种clone方法并为浅表复制提供了支持。
在此处输入图片说明

深度复制:
深度复制是在将对象及其引用的对象一起复制时发生的。下图显示了obj1在其上执行深度复制之后的情况。不仅已obj1被复制,而且其中包含的对象也已被复制。我们可以Java Object Serialization用来制作深层副本。不幸的是,这种方法也存在一些问题(详细示例)。
在此处输入图片说明

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

  1. 如果您有一个对象,就知道它有一个公共clone()方法,但是在编译时却不知道该对象的类型,那么就遇到了问题。Java有一个名为的接口Cloneable。实际上,如果要创建对象,则应实现此接口CloneableObject.cloneprotected,因此我们必须使用公共方法覆盖它,以便对其进行访问。
  2. 当我们尝试对复杂对象进行深度复制时,会出现另一个问题。假设所有成员对象变量的方法也都进行深层复制,那么这样做的风险太大。您必须控制所有类中的代码。clone()

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

  • cloneBean 即使bean类本身未实现Cloneable,也会根据可用的属性getter和setter克隆bean。
  • copyProperties 在属性名称相同的所有情况下,都会将属性值从原始Bean复制到目标Bean。

1
您能解释一下另一个对象包含什么吗?
Freakyuser

1
@Chandra Sekhar“浅复制会创建相同类的新实例,并将所有字段复制到该新实例,然后将其返回”,这是错误的所有字段,bcz对象不会被复制,仅引用会指向老(原始)指向的同一对象。
JAVA 2013年

4
@sunny-Chandra的描述是正确的。您对发生的情况的描述也是如此;我是说您对“复制所有字段”的含义有不正确的理解。该字段引用,不是引用的对象。“复制所有字段” 是指 “复制所有那些引用”。最好指出的是,对于任何与您有相同误解的人来说,“复制所有字段”这一语句究竟意味着什么。:)
ToolmakerSteve

2
...如果我们以某种较低级的OO语言来思考,并带有指向对象的“指针”,则该字段将包含内存中找到对象数据的地址(例如“ 0x70FF1234”)。该地址是被复制(分配)的“字段值”。您是正确的,最终结果是两个对象都具有引用(指向)同一对象的字段。
ToolmakerSteve

127

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

SerializationUtils.clone(Object);

例:

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

59
只要对象实现Serializable
Androiderson

2
在这种情况下,如果最后一个对象是静态的,则克隆对象将不引用原始对象。
但丁

8
第三方库仅用于克隆对象!
可汗

2
@汗,“一个第三方图书馆只是去”是一个完全独立的讨论!:D
查尔斯·伍德

103

请按照以下步骤操作:

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

1
你测试了吗?我可以在我的项目中使用它,正确无误非常重要。
朦胧的

2
@misty我已经测试过了。在我的生产应用程序上完美运行
Andrii Kovalchuk 16/09/27

克隆后,当您修改原始对象时,它也在修改克隆。
锡比什

4
这是错误的,因为它不是要求的深层副本。
Bluehorn,

1
该方法克隆了指向可克隆对象的指针,但是两个对象中的所有属性都相同,因此在内存中创建了一个新对象,但是每个对象中的数据都是来自内存的相同数据
Omar HossamEldin

40

为什么没有使用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;
        }
    }

这看起来好多了,但是您只需要考虑最终字段,因为setAccessible(true)可能会失败,因此也许您需要分别处理分别调用field.set(clone,field.get(obj))时引发的IllegalAccessException异常。
2014年

1
我非常喜欢它,但是您可以重构它以使用泛型吗?私人静态<T> T cloneObject(T obj){....}
阿德林

2
我认为这是个问题,当我们从属性中引用它给父母时: Class A { B child; } Class B{ A parent; }
nhthai

即使在这种情况下它也会失败,需要处理,我明天会继续玩下去。 class car { car car = new car(); }
扬·阿布恰(JánЯabčan)

2
这容易出错。不确定如何处理收藏品
ACV

31

我使用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;
}
}

这很棒。但是请注意,如果您尝试克隆List <Integer>之类的内容。这将是越野车,我的整数变成了Doubles,100.0。我花了很长时间才了解他们为什么会这样。解决方案是逐个克隆Integers,然后循环添加到列表中。
paakjis,


14

Cloneable在课程中添加以下代码

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

用这个 clonedObject = (YourClass) yourClassObject.clone();



12

这也可以。假设模型

class UserAccount{
   public int id;
   public String name;
}

首先添加 compile 'com.google.code.gson:gson:2.8.1'到您的应用>渐变和同步。然后

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

您可以通过transient在访问修饰符后使用关键字来排除使用字段。

注意:这是错误的做法。也建议不要使用CloneableJavaSerialization速度慢且损坏。编写副本构造函数以获得最佳性能ref

就像是

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

90000次迭代的测试统计数据:
UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);耗时808ms

线UserAccount clone = new UserAccount(aO);少于1ms

结论:如果老板发疯并且更喜欢速度,请使用gson。如果您更喜欢质量,请使用第二个副本构造函数。

您还可以在Android Studio中使用复制构造函数代码生成器插件


如果这是不好的做法,为什么要提出建议呢?
Parth Mehrotra

感谢@ParthMehrotra现在得到了改进
Qamar



9

深克隆是您的答案,这需要实现Cloneable接口并覆盖clone()方法。

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

你会这样称呼它 DummyBean dumtwo = dum.clone();


2
dummy,a String,是不可变的,您无需复制它
Steve Kuo 2015年

7

为此,您必须以某种方式克隆对象。尽管Java具有克隆机制,但不必使用它。创建一个可以为您完成复制工作的复制方法,然后执行以下操作:

dumtwo = dum.copy();

是有关完成复印的不同技术的更多建议。


6

除了显式复制之外,另一种方法是使对象不可变(no set或其他mutator方法)。这样就永远不会出现问题。对于较大的对象,不变性变得更加困难,但另一方面是,它会将您推向分裂为连贯的小对象和合成物的方向。


5

替代egaga的复制构造函数方法。您可能已经有一个POJO,所以只需添加另一个方法即可copy()返回初始化对象的副本。

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

如果您已经有DummyBean并想要一份副本:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

输出:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

但是两者都运作良好,最终取决于您...



3

您可以使用XStream从http://x-stream.github.io/自动进行深度复制:

XStream是一个简单的库,用于将对象序列化为XML并再次返回。

将其添加到您的项目(如果使用maven)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

然后

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

有了它,您无需执行任何克隆接口即可获得副本。


29
到XML的转换似乎并不十分优雅。说得客气一点!
Timmmm 2012年

看一下也可以java.beans.XMLEncoder序列化为XML的标准Java API(尽管并非完全出于深度复制目的)。
Jaime Hablutzel

1
您知道这有多重吗?
mahieddine '16

1
在我看来,这需要很多开销,因为您需要添加一个第三方库并执行对象序列化,这很可能会对性能产生巨大影响。
NiThDi '16

2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

并在您的代码中:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
如果尝试捕获异常且未抛出异常,则声明中的“ throws CloneNotSupportedException”集中没有任何意义。因此,您可以删除它。
基督教徒

2

传递要复制的对象并获取所需的对象:

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

现在解析objDest到所需的对象。

编码愉快!


1

您可以尝试实现Cloneable和使用该clone()方法。但是,如果您使用克隆方法,则应该-按照标准-始终覆盖Objectpublic Object clone()方法。


1

如果你可以添加注释的源文件,注释处理器或代码生成像这样一个可以使用。

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

DummyBeanBuilders将生成一个类,该类具有dummyBeanUpdater用于创建浅表副本的静态方法,与您手动执行的方法相同。

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

0

使用gson用于复制的对象。

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

假设我有一个对象person

Person copyPerson = copyObject(person);
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.