870

考虑下面的代码:

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影响. 但是上面的代码并没有这样做。当我改变一些东西时,同样的改变也在发生。dumdumtwodumdumtwo

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

4

24 回答 24

673

创建一个复制构造函数:

class DummyBean {
  private String dummy;

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

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

于 2009-05-15T14:35:42.483 回答
439

基础: Java 中的对象复制。

让我们假设一个 object- obj1,它包含两个对象containsObj1containsObj2
在此处输入图像描述

浅拷贝:
浅拷贝创建一个新instance的相同类并将所有字段复制到新实例并返回它。Object 类提供了一种clone方法并提供了对浅拷贝的支持。
在此处输入图像描述

深拷贝:
一个对象连同它所引用的对象一起被拷贝时,就会发生深拷贝。下图显示obj1了对其执行深层复制后的情况。不仅obj1被复制了,而且其中包含的对象也被复制了。我们可以Java Object Serialization用来制作深拷贝。不幸的是,这种方法也存在一些问题(详细示例)。
在此处输入图像描述

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

  1. 如果你有一个对象,你知道它有一个公共clone()方法,但你在编译时不知道对象的类型,那么你就有问题了。Java 有一个名为Cloneable. 在实践中,如果我们想制作一个对象,我们应该实现这个接口CloneableObject.clone受保护的,所以我们必须用公共方法覆盖它才能访问它。
  2. 当我们尝试对复杂对象进行深度复制时,会出现另一个问题。假设所有成员对象变量的方法也做深拷贝,这个假设风险太大。您必须控制所有类中的代码。clone()

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

  • cloneBean将根据可用的属性 getter 和 setter 克隆 bean,即使 bean 类本身没有实现 Cloneable。
  • copyProperties对于属性名称相同的所有情况,会将属性值从源 bean 复制到目标 bean。
于 2012-03-23T05:47:34.717 回答
149

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

SerializationUtils.clone(Object);

例子:

this.myObjectCloned = SerializationUtils.clone(this.object);
于 2013-05-02T19:45:03.023 回答
109

只需按照以下说明:

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
于 2010-05-17T09:27:25.760 回答
45

为什么使用反射 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;
        }
    }
于 2014-08-16T09:27:25.860 回答
33

我使用 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;
}
}
于 2013-07-09T20:14:24.277 回答
24

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

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

参考这里:对象复制

于 2009-05-15T14:36:25.430 回答
15

将代码添加Cloneable到您的课程中

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

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

于 2013-07-31T15:00:04.060 回答
14

深度克隆是您的答案,这需要实现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();

于 2014-07-08T17:14:09.310 回答
12

这也有效。假设模型

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

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

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

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

注意:这是不好的做法。也不建议使用Cloneable,否则JavaSerialization它很慢而且坏掉了。编写复制构造函数以获得最佳性能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 中使用复制构造函数代码生成器插件。

于 2017-01-26T08:50:46.297 回答
10

是的。您需要深度复制您的对象。

于 2009-05-15T14:35:02.393 回答
10

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

这里:克隆(Java方法)

于 2009-05-15T14:35:32.327 回答
10

使用深度克隆实用程序:

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

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

于 2011-10-23T16:12:06.923 回答
8

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

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所需的对象。

快乐编码!

于 2014-12-23T12:46:35.313 回答
7

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

dumtwo = dum.copy();

以下是有关完成副本的不同技术的更多建议。

于 2009-05-15T14:33:07.623 回答
7

替代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

但两者都运作良好,这最终取决于你......

于 2019-09-19T08:29:23.017 回答
7

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

注意:性能要慢得多。

于 2020-05-14T17:16:02.440 回答
6

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

于 2009-05-15T14:43:35.163 回答
4
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
于 2014-03-02T13:35:20.950 回答
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));

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

于 2011-07-25T16:59:42.950 回答
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();
于 2018-02-04T08:27:50.183 回答
1

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

于 2009-05-15T19:49:59.733 回答
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();
于 2017-02-05T16:17:11.430 回答
1

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

爪哇

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

===============================================

科特林更新

如果您使用数据类,那么您将拥有复制 Kotlin 数据类的复制方法。很酷的事情是您还可以传递一些值来使用新副本修改对象。我会推荐这种方式。

例子:

//班级

data class TestModel(val title: String, var subtitle: String)

用例

val testClass = TestModel("Test title", "Test subtitle")

val newInstance = testClass.copy(subtitle = "new subtitle for copy instance")
于 2021-06-15T18:43:58.383 回答