簡體   English   中英

在Java中復制對象而不影響原始的via拷貝構造函數

[英]Copying an Object in Java without affecting the original via copy constructor

我正在嘗試復制一個Object,然后修改它,而不更改原始對象。

我找到了這個解決方案 ,似乎最好的方法是復制構造函數 - 從我的理解,這將給我一個深層復制(一個完全獨立的對象與原始)。

所以我試過了。 但是,我注意到,當執行以下代碼時,它會影響復制它的所有先前對象。 當我調用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();
    }
}

究竟是什么問題呢? 復制構造函數不是那樣工作的嗎? 我編碼錯誤的方式是什么?

這是你的拷貝構造函數中的問題:

this.questionList = incoming.getQuestionList();

那只是將引用復制到列表中。 兩個對象仍然會引用同一個對象。

您可以使用:

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

創建原始列表的副本 - 但如果Question本身是可變的,這仍然不夠好。 在這種情況下,您必須創建每個Question對象的副本以實現完全隔離。

你的其他字段沒問題,因為它們是原語或String引用(它是不可變的,允許你安全地共享引用)。

這個

this.questionList = incoming.getQuestionList();

最有可能復制對原始列表的引用 (我說可能因為getQuestionList()可能會給你一個防御性的副本)。 您可能需要制作該列表的新副本。 也許是包含的Question對象。 也許他們引用的任何東西。

這是深拷貝的問題。 為了可靠地執行此操作,您必須復制所有可變對象。 請注意,如果一個對象是不可變的 (例如字符串),那么它們就無法更改,因此您可以引用原始文件,確信它們不會被更改。 這同樣適用於原語。 在代碼庫中鼓勵不變性的一個很好的理由。

如果你不能創建一個不可變的類,寫你的類,使它成為防御性副本。 即當客戶要求收集時,它應該復制並返回。 否則你所謂善意的客戶可能會改變你的內部狀態(無意或無意)。

創建深層副本時的問題是,除非您在其上使用特定的深層復制構造函數,否則通過引用復制非基本類型的所有內容。

在你的特定情況下你沒有boolintString變量的問題,因為你通過值傳遞它們(實際上String是通過引用傳遞但它是不可變的,因此沒有問題)但是你傳遞的是ArrayList<Question> questionList 當你這樣做

this.object = incoming.object

你只需復制一個參考。 因此, 兩個變量都指向內存中的同一個對象 ,因此您不會深入復制它。 您必須創建具有相同內部值的對象的另一個實例,然后您將確定,例如this.object = new YourObject(incoming.object)

請注意,通常意味着在合成樹中你的課程更復雜,你必須深入研究變量,直到你復制它們為止。

如果我們需要復制一個簡單的pojo(非嵌套)。 然后淺拷貝就足夠了。

克隆人階級

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM