【问题标题】:Copying an Object in Java without affecting the original via copy constructor通过复制构造函数在 Java 中复制对象而不影响原始对象
【发布时间】:2012-11-24 14:39:02
【问题描述】:

我正在尝试复制一个对象,然后将对其进行修改,而不更改原始对象。

我找到了this solution,似乎最好的方法是复制构造函数 - 据我了解,这会给我一个深层副本(与原始对象完全分开的对象)。

所以我尝试了。但是,我注意到当下面的代码执行时,它会影响之前复制它的所有对象。当我调用surveyCopy.take() 时,这将更改Survey 内部的值,它也会更改 selectedSurvey 内部的值。

public class MainDriver {
...
//Code that is supposed to create the copy
case "11":  selectedSurvey = retrieveBlankSurvey(currentSurveys);
            Survey surveyCopy = new Survey(selectedSurvey);
            surveyCopy.take(consoleIO);
            currentSurveys.add(surveyCopy);
            break;
}

这是我的复制构造函数的代码:

public class Survey implements Serializable
{
    ArrayList<Question> questionList;
    int numQuestions;
    String taker;
    String surveyName;
    boolean isTaken;

    //Copy constructor
    public Survey(Survey incoming)
    {
        this.taker = incoming.getTaker();
        this.numQuestions = incoming.getNumQuestions();
        this.questionList = incoming.getQuestionList();
        this.surveyName = incoming.getSurveyName();
        this.isTaken = incoming.isTaken();
    }
}

那么问题到底是什么?复制构造函数不能那样工作吗?是不是我写错了?

【问题讨论】:

    标签: java object constructor copy


    【解决方案1】:

    如果我们需要复制一个简单的 pojo(不是嵌套的)。那么浅拷贝就够了。

    克隆类

    导入 java.lang.reflect.Field;

    public class Cloner {
        public static <T> T cloneShallow(T srcEntity, T destEntity){
            try {
                return copy(srcEntity, destEntity);
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
    
        private static <T> T copy(T srcEntity, T destEntity) throws IllegalAccessException, InstantiationException {
            if(srcEntity == null){
                return null;
            }
    
            Class<?> clazz = srcEntity.getClass();
    
            T newEntity;
    
            if(destEntity != null){
                newEntity = destEntity;
            }else{
                //create new instance
                newEntity = (T) srcEntity.getClass().newInstance();
            }
    
            while (clazz != null) {
                copyFields(srcEntity, newEntity, clazz);
                clazz = clazz.getSuperclass();
            }
    
            return newEntity;
        }
    
        private static  <T> T copyFields(T entity, T newEntity, Class<?> clazz) throws IllegalAccessException {
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                field.set(newEntity, field.get(entity));
            }
            return newEntity;
        }
    }
    

    让我们打电话..

    eg.
    Apple apple = new Apple();
    apple.setColor("Green");
    
    Apple newApple = Cloner.cloneShallow(apple, new Apple());
    ( or )
    Apple newApple = Cloner.cloneShallow(apple, null);
    

    【讨论】:

      【解决方案2】:

      创建深拷贝时的问题是,所有不是原始类型的东西都是通过引用复制的,除非您也在其上使用特定的深拷贝构造函数。

      在您的具体情况下,boolintString 变量没有问题,因为您通过值传递它们(实际上String 是通过引用传递但它是不可变的,所以没有问题)但是你正在传递一个ArrayList&lt;Question&gt; questionList。当你这样做时

      this.object = incoming.object
      

      你只是复制一个参考。所以两个变量都指向内存中的同一个对象,所以你没有深度复制它。您必须创建具有相同内部值的对象的另一个实例,然后您会确定,例如this.object = new YourObject(incoming.object)

      请注意,这通常意味着您的类在组合树中越复杂,您将不得不深入研究变量,直到将它们全部复制。

      【讨论】:

        【解决方案3】:

        这个

        this.questionList = incoming.getQuestionList();
        

        最有可能将 reference 复制到原始列表(我说 可能,因为 getQuestionList() 可能会给您一个防御性副本)。您可能必须制作该列表的新副本。也许包含的Question 对象。或许还有他们引用的任何东西。

        这是深拷贝的问题。为了可靠地做到这一点,您必须复制所有可变对象。请注意,如果一个对象是不可变(例如字符串),那么它们就不能被更改,因此您可以参考原件,确信它们不会被更改。这同样适用于原语。鼓励代码库中不变性的一个很好的理由。

        如果您无法创建不可变类,请编写您的类,使其生成防御性副本。即当客户要求它收集时,它应该复制并返回。否则,您所谓的善意客户可能会改变您的内部状态(无意或无意)。

        【讨论】:

        • 肯定复制参考
        • 我同意你的观点。我试图表明该方法可以返回一个防御性副本(即它会创建一个副本然后返回它)。无论如何,参考副本会出现在某处
        • @mishadoff - 不一定。我们还没有看到 getter 代码,并且一些 getter 实际上创建了一个防御性副本(这对于深度克隆来说可以正常工作)。我认为“最有可能复制参考”是正确的解释。
        • 感谢您的回复。使所有对象不可变有什么缺点吗?如何使它们不可变?
        【解决方案4】:

        这就是问题所在,在您的复制构造函数中:

        this.questionList = incoming.getQuestionList();
        

        这只是将 reference 复制到列表中。两个对象仍将引用同一个对象。

        你可以使用:

        this.questionList = new ArrayList<Question>(incoming.getQuestionList());
        

        创建原始列表的副本 - 但如果Question 本身是可变的,这仍然不够好。在这种情况下,您必须为每个 Question 对象创建一个副本以实现完全隔离。

        您的其他字段没问题,因为它们要么是原语,要么是对 String 的引用(这是不可变的,允许您安全地共享引用)。

        【讨论】:

        • 感谢您的回复。听起来我应该让Question 不可变,因为我不确定它是否是。我怎样才能做到这一点?编辑:也许这不是最好的主意,因为在 Question 内部有更多不可变的对象。我有点难过。
        • @iaacp:是的,如果你可以让Question 不可变,那会有所帮助。如果它的所有字段都已经是不可变类型,那应该使它更容易而不是更难。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-14
        • 2015-01-05
        • 1970-01-01
        • 1970-01-01
        • 2015-01-30
        • 2016-01-10
        相关资源
        最近更新 更多