在java中复制构造函数而不是克隆

时间:2017-08-31 14:18:19

标签: java reference clone copy-constructor

我正在尝试在java中实现一个复制构造函数。我面临着类的非原始类型字段的问题。在创建新副本时,它正在共享成员。例如

public class Bad implements Cloneable {
    private ArrayList<Integer> a;
    private Object c;

    public static void main(String[] args) {
        Bad b1 = new Bad();
        b1.a.add(10);

        System.out.println(b1.a);
        Bad b2 = b1.clone();
        b2.a.add(12);

        System.out.println(b1.a);   
    }

    Bad() {
        a = new ArrayList<>();
        c = null;
    }

    Bad(Bad b) {
        a = b.a;
        c = b.c;
    }

    public Bad clone() {
        return new Bad(this);
    }
}

结果是:

[10]
[10, 12]

我不希望这种情况发生。以此为例。我原来的问题包括更多用户定义的字段 或者有没有图书馆为我工作?提前谢谢。

3 个答案:

答案 0 :(得分:3)

复制构造函数的简单规则:

  • 原始值可以按原样复制;它们只是没有单独身份的价值观
  • 对不可变类型的引用(例如,String,Integer,任何枚举类常量)也可以按原样复制;虽然原始对象和复制对象将共享相同的引用,但引用的对象是不可变的并且永远不会更改
  • 对可变类型的引用(例如,Date,ArrayList,任何数组)必须复制到该类型的新实例中;否则原始和复制的对象将共享对同一可变字段对象的引用(这不是你想要的)

复制一个只包含具有原始值和不可变值的字段的对象是一种简单的模式。

复制其字段包含可变对象的对象可能会使该过程变得极其昂贵,具体取决于可变对象的复杂程度(想象一个包含其值也是Maps的Map的ArrayList)。但是,如果您希望获得安全副本,则制作可变字段的新副本是必不可少的。

答案 1 :(得分:2)

整数是不可变的,但你需要创建一个全新的 ArrayList ,我的意思是:

Bad(Bad b) {
    a = b.a;
    c = b.c;
}

改为

Bad(Bad b) {
    a = new ArrayList<>(b.a);
    c = // this here must be copy constructed too
}

然后你会得到

  

[10]

     

[10]

答案 2 :(得分:1)

正确的方法是创建列表的新实例,而不是将引用传递给原始列表。

Bad(Bad b) {
  a = new ArrayList<>(b.a);
  c = b.c; // this should call clone or something similar as well
}

另请注意,如果你在ba列表中有一些非原始类型,那么你也必须复制/克隆所有子元素(现在不需要它,因为你在其中有Integer,这是不可变的。)