如何在Java中复制对象?

Vee*_*era 763 java clone copy object

请考虑以下代码:

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'
Run Code Online (Sandbox Code Playgroud)

所以,我想复制dumdumtwo并更改dum而不影响dumtwo.但上面的代码并没有这样做.当我改变某些内容时dum,也会发生同样的变化dumtwo.

我想,当我说dumtwo = dum,Java 复制引用.那么,有没有办法创建一个新的副本dum并分配给它dumtwo

ega*_*aga 598

创建一个复制构造函数:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @MatthewPiziak,对我来说 - 这不会是深度克隆,因为任何嵌套对象仍然会引用原始源实例,而不是重复,除非每个引用(非值类型)对象提供与上面相同的构造函数模板. (118认同)
  • 但是他必须将他的代码改为DummyBean two = new DummyBean(one); 对? (41认同)
  • @Timmmm:是的,他们会引用相同的String,但因为它是不可变的,所以没问题.原语也是如此.对于非基元,您只需递归复制构造函数调用.例如,如果DummyBean引用了FooBar,那么FooBar应该有构造函数FooBar(另一个是FooBar),而dummy应该调用this.foobar = new FooBar(another.foobar) (14认同)
  • 这种方法是否有效地完成了与深层复制相同的操作? (12认同)
  • @ChristianVielma:不,它不会是"johndoe".就像Timmmm所说,字符串本身是不可变的.使用一个setDummy(..),您可以将引用设置为一个指向"johndoe",但不能指向一个. (6认同)
  • @Chris Kaminski正确 (4认同)
  • 有人可以确认这是否真的有效吗?它看起来会创建另一个DummyBeam实例,但两个虚拟仍将引用相同的String ... (4认同)
  • 我真的不喜欢`copy constructor`,因为不清楚构造函数是否会复制实例而不是简单地获取某些值或使用他的引用.我更喜欢使用更全面的克隆方法.使用`DummyBean DummyBean.clone()`,您可以轻松地告诉输出但构造函数是模糊的. (3认同)
  • 如果你这样做:DummyBean two = new DummyBean(one); one.setDummy( "输入johndoe"); 那么two.getDummy()将是"johndoe",所以这不是一个正确的答案. (2认同)
  • 此解决方案不适用于抽象基类,这使得它在许多OOP设计模式中毫无用处. (2认同)
  • 如果你有 100 个字段,这很难做到。 (2认同)

Cha*_*har 392

基本: Java中的对象复制.

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

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

深度复制:
对象与其引用的对象一起复制时,会发生深层复制.下图显示obj1了对其进行深度复制后的情况.不仅obj1已复制,而且其中包含的对象也已被复制.我们可以Java Object Serialization用来做一个深层复制.不幸的是,这种方法也有一些问题(详细的例子).
在此输入图像描述

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

  1. 如果你有一个对象,你知道有一个公共clone()方法,但是你在编译时不知道对象的类型,那么你就有问题了.Java有一个名为的接口Cloneable.实际上,如果我们想要创建一个对象,我们应该实现这个接口Cloneable.Object.clone保护的,所以我们必须重写与公共方法,以它为它进行访问.
  2. 当我们尝试另一个问题是深复制一个的复杂的对象.假设clone()所有成员对象变量的方法也进行深度复制,这对假设来说风险太大.您必须控制所有类中的代码.

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

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

  • @sunny - Chandra的描述是正确的.你对所发生的事情的描述也是如此; 我说你对"复制所有领域"的含义有不正确的理解.字段**是**参考,它不是被引用的对象."复制所有字段"**表示**"复制所有这些引用".对于任何与你有相同错误解释的人来说,对于"复制所有字段"的陈述,你指出这究竟意味着什么是好的.:) (4认同)
  • ...如果我们根据一些较低级别的OO语言来考虑对象的"指针",这样的字段将包含在其中找到对象数据的内存中的地址(例如"0x70FF1234").该地址是正在复制(分配)的"字段值".你是对的,最终结果是两个对象都有引用(指向)同一对象的字段. (2认同)

小智 119

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

SerializationUtils.clone(Object);
Run Code Online (Sandbox Code Playgroud)

例:

this.myObjectCloned = SerializationUtils.clone(this.object);
Run Code Online (Sandbox Code Playgroud)

  • 只要对象实现`Serializable` (51认同)
  • 第三方库只是为了克隆对象! (7认同)
  • @Khan,“第三方库只是为了”是一个完全独立的讨论!:D (3认同)
  • 在这种情况下,如果最后一个对象是静态的,则克隆对象将不引用原始对象。 (2认同)

小智 101

请按以下步骤操作:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

  • 这是错误的,因为它不是*要求的深拷贝. (4认同)
  • 你测试过这个吗?我可以将其用于我的项目,并且正确性很重要。 (2认同)
  • @misty我已经测试过了.在我的制作应用程序上完美运行 (2认同)
  • 克隆后,当您修改原始对象时,它也会修改克隆对象。 (2认同)
  • 该方法克隆了指向可克隆对象的指针,但是两个对象内部的所有属性都是一样的,所以在内存中创建了一个新对象,但是每个对象内部的数据都是来自内存的相同数据 (2认同)

Wil*_*ner 37

为什么没有使用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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

这很简单.

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

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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 我认为当我们从属性中引用父母时会出现问题:'A类{B孩子; B类{父母; }` (2认同)
  • 这容易出错。不确定如何处理集合 (2认同)

Pet*_*ter 28

我使用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;
}
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*isb 22

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

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

请参阅此处:对象复制


bru*_*nde 12

是.您需要深层复制对象.

  • 这可能是我在 stackoverflow 上看到的最没有帮助的答案。 (12认同)

Tej*_*idu 12

Cloneable在您的班级中添加和下面的代码

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
Run Code Online (Sandbox Code Playgroud)

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


Qam*_*mar 11

这也有效.假设模型

class UserAccount{
   public int id;
   public String name;
}
Run Code Online (Sandbox Code Playgroud)

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

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);
Run Code Online (Sandbox Code Playgroud)

您可以transient在访问修饰符后使用关键字排除使用字段.

注意:这是不好的做法.也不建议使用CloneableJavaSerialization它的速度慢而且破碎.编写复制构造函数以获得最佳性能参考.

就像是

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

线路UserAccount clone = new UserAccount(aO);不到1 毫秒

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

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


Jon*_*rst 10

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

这里:克隆(Java方法)


Coj*_*nes 8

使用深度克隆实用程序:

SomeObjectType copy = new Cloner().deepClone(someObject);
Run Code Online (Sandbox Code Playgroud)

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


abb*_*bas 8

深度克隆是您的答案,需要实现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;
   }
}
Run Code Online (Sandbox Code Playgroud)

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

  • `dummy`,一个`String`,是不可变的,你不需要复制它 (2认同)

A-D*_*ech 7

传递你想要复制的对象并获取你想要的对象:

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;

    }
Run Code Online (Sandbox Code Playgroud)

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

快乐编码!


Pie*_*rre 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
}
Run Code Online (Sandbox Code Playgroud)

如果您已经有一个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());
Run Code Online (Sandbox Code Playgroud)

输出:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2
Run Code Online (Sandbox Code Playgroud)

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


Yis*_*hai 6

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

dumtwo = dum.copy();
Run Code Online (Sandbox Code Playgroud)

以下是有关完成复制的不同技术的更多建议.


Tom*_*ine 5

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


tuh*_*n47 5

使用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());
}
Run Code Online (Sandbox Code Playgroud)

假设我有一个对象person。所以

Person copyPerson = copyObject(person);
Run Code Online (Sandbox Code Playgroud)

注意:性能要慢得多。