如何在Java中复制对象?

时间:2009-05-15 14:30:27

标签: java object copy clone

请考虑以下代码:

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

24 个答案:

答案 0 :(得分:574)

创建一个复制构造函数:

class DummyBean {
  private String dummy;

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

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

答案 1 :(得分:381)

基本:用Java复制对象。

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

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

深层复制:
对象与其引用的对象一起复制时,会发生深层复制。下图显示了对其进行深层复制后的obj1不仅已复制obj1 ,而且其中包含的对象也已被复制。我们可以使用Java Object Serialization制作深层副本。不幸的是,这种方法也存在一些问题(detailed examples)。
enter image description here

可能出现的问题:
clone正确实施很棘手 最好使用Defensive copyingcopy constructors(作为@egaga回复)或static factory methods

  1. 如果你有一个对象,你知道有一个公共clone()方法,但是你在编译时不知道对象的类型,那么你就有问题了。 Java有一个名为Cloneable的接口。实际上,如果我们想要创建一个对象Cloneable,我们应该实现这个接口。 Object.clone 受保护,因此我们必须使用公共方法覆盖,以便可以访问它。
  2. 当我们尝试深层复制 复杂对象时,会出现另一个问题。假设所有成员对象变量的clone()方法也进行深度复制,这对假设来说风险太大。您必须控制所有类中的代码。
  3. 例如,org.apache.commons.lang.SerializationUtils将使用序列化(Source)的深度克隆方法。如果我们需要克隆Bean,那么org.apache.commons.beanutilsSource)中有几种实用方法。

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

答案 2 :(得分:109)

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

SerializationUtils.clone(Object);

示例:

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

答案 3 :(得分:96)

请按以下步骤操作:

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

答案 4 :(得分:36)

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

答案 5 :(得分:26)

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

答案 6 :(得分:22)

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

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

Refer here: Object copying

答案 7 :(得分:13)

是。您需要Deep Copy您的对象。

答案 8 :(得分:12)

Cloneable及以下代码添加到您的班级

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

使用此clonedObject = (YourClass) yourClassObject.clone();

答案 9 :(得分:10)

如果您最终需要clone(),这是一个不错的解释......

Here: clone (Java method)

答案 10 :(得分:9)

这也有效。假设模型

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

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

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);行需要 808毫秒

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

结论:如果你的老板疯了,你更喜欢速度,请使用gson。如果您更喜欢质量,请使用第二个复制构造函数。

您还可以在Android Studio中使用复制构造函数代码generator plugin

答案 11 :(得分:8)

使用深度克隆实用程序:

SomeObjectType copy = new Cloner().deepClone(someObject);

这将深层复制任何java对象,请在https://github.com/kostaskougios/cloning

查看

答案 12 :(得分:7)

深度克隆是您的答案,需要实施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, 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

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

答案 13 :(得分:6)

要做到这一点,你必须以某种方式克隆对象。尽管Java具有克隆机制,但如果您不需要,请不要使用它。创建一个复制方法,使复制工作为您,然后执行:

dumtwo = dum.copy();

Here是关于完成副本的不同技术的更多建议。

答案 14 :(得分:5)

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

答案 15 :(得分:3)

class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}

答案 16 :(得分:2)

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

  

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

有了这个,您就可以获得一个副本而无需实现任何克隆接口。

答案 17 :(得分: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解析为所需的对象。

快乐的编码!

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

答案 19 :(得分:2)

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

注意:性能要慢得多。

答案 20 :(得分:1)

您可以尝试实施Cloneable并使用clone()方法;但是,如果您使用克隆方法,您应该 - 通过标准 - 始终覆盖Object的{​​{1}}方法。

答案 21 :(得分:1)

如果您可以为源文件添加注释,可以使用注释处理器或代码生成器,如this one

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

答案 22 :(得分:1)

替代egaga's复制的构造方法。您可能已经有一个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

但是两者都很好,最终取决于您...

答案 23 :(得分:0)

使用 Kotlin 扩展功能

fun <T : Any?> T.duplicate(): T? {
    var copyObject: T? = null
    try {
        val byteArrayOutputStream = ByteArrayOutputStream()
        val objectOutputStream = ObjectOutputStream(byteArrayOutputStream)
        objectOutputStream.writeObject(this)
        objectOutputStream.flush()
        objectOutputStream.close()
        byteArrayOutputStream.close()
        val byteData = byteArrayOutputStream.toByteArray()
        val byteArrayInputStream = ByteArrayInputStream(byteData)
        try {
            copyObject = ObjectInputStream(byteArrayInputStream).readObject() as T
        } catch (e: ClassNotFoundException) {
            e.printStackTrace()
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return copyObject
}

用例

var object = Any()
var duplicateObject = object.duplicate()

Java

<T extends Object> T copyObject(T sourceObject) {

    T copyObject = null;

    try {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(sourceObject);
        objectOutputStream.flush();
        objectOutputStream.close();
        byteArrayOutputStream.close();
        byte[] byteData = byteArrayOutputStream.toByteArray();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteData);
        try {
            copyObject = (T) new ObjectInputStream(byteArrayInputStream).readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return copyObject;
}

用例

Object object = new Object();
Object duplicateObject = copyObject(object);