复制构造函数和防御性复制

Posted

技术标签:

【中文标题】复制构造函数和防御性复制【英文标题】:Copy constructors and defensive copying 【发布时间】:2013-02-22 09:31:15 【问题描述】:

什么是复制构造函数

有人可以分享一个有助于理解防御性复制原则的小例子吗?

【问题讨论】:

javapractices.com/topic/TopicAction.do?Id=12 你可以在这里找到防御性复制的详细解释:Defensive copying。 【参考方案1】:

这是一个很好的例子:

class Point 
  final int x;
  final int y;

  Point(int x, int y) 
    this.x = x;
    this.y = y;
  

  Point(Point p) 
    this(p.x, p.y);
  


注意构造函数 Point(Point p) 如何获取 Point 并复制它 - 那是 copy constructor

这是一个defensive 副本,因为原始Point 通过复制它来防止更改。

那么现在:

// A simple point.
Point p1 = new Point(3,42);
// A new point at the same place as p1 but a completely different object.
Point p2 = new Point(p1);

请注意,这不一定是创建对象的正确方式。然而,这是一种创建对象的方法,可以确保您不会意外地对同一个对象进行两次引用。显然,如果这是您想要实现的目标,这只是一件好事。

【讨论】:

【参考方案2】:

在 C++ 中经常看到的复制构造函数在部分隐藏、自动调用的操作中需要它们。

java java.awt.PointRectangle 浮现在脑海中;也是非常古老的可变对象。

通过使用不可变对象,如StringBigDecimal,只需分配对象引用即可。事实上,由于Java在C++之后的早期阶段,仍然存在一个 String 中的愚蠢复制构造函数:

public class Recipe 
    List<Ingredient> ingredients;

    public Recipe() 
        ingredients = new ArrayList<Ingredient>();
    

    /** Copy constructor */
    public Recipe(Recipe other) 
        // Not sharing: ingredients = other.ingredients;
        ingredients = new ArrayList<>(other.ingredients);
    

    public List<Ingredient> getIngredients() 
        // Defensive copy, so others cannot change this instance.
        return new ArrayList<Ingredient>(ingredients);
        // Often could do:
        // return Collections.immutableList(ingredients);
    


根据要求

使用复制构造函数泄漏类:

public class Wrong 
    private final List<String> list;

    public Wrong(List<String> list) 
        this.list = list; // Error: now shares list object with caller.
    

    /** Copy constructor */
    public Wrong(Wrong wrong) 
        this.list = wrong.list; // Error: now shares list object with caller.
    

    public List<String> getList() 
        return list; // Error: now shares list object with caller.
    

    public void clear() 
        list.clear();
    

使用复制构造函数的正确类:

public class Right 
    private final List<String> list;

    public Right(List<String> list) 
        this.list = new ArrayList<>(list);
    

    public Right(Right right) 
        this.list = new ArrayList<>(right.list);
    

    public List<String> getList() 
        return new ArrayList<>(list);
    

    public List<String> getListForReading() 
        return Collections.unmodifiableList(list);
    

    public void clear() 
        list.clear();
    

带有测试代码:

public static void main(String[] args) 
    List<String> list1 = new ArrayList<>();
    Collections.addAll(list1, "a", "b", "c", "d", "e");
    Wrong w1 = new Wrong(list1);
    list1.remove(0);
    System.out.printf("The first element of w1 is %s.%n", w1.getList().get(0)); // "b"
    Wrong w2 = new Wrong(w1);
    w2.clear();
    System.out.printf("Size of list1 %d, w1 %d, w2 %d.%n",
        list1.size(), w1.getList().size(), w2.getList().size());

    List<String> list2 = new ArrayList<>();
    Collections.addAll(list2, "a", "b", "c", "d", "e");
    Right r1 = new Right(list2);
    list2.remove(0);
    System.out.printf("The first element of r1 is %s.%n", r1.getList().get(0)); // "a"
    Right r2 = new Right(r1);
    r2.clear();
    System.out.printf("Size of list2 %d, r1 %d, r2 %d.%n",
        list2.size(), r1.getList().size(), r2.getList().size());

这给出了:

The first element of w1 is b.
Size of list1 0, w1 0, w2 0.
The first element of r1 is a.
Size of list2 4, r1 5, r2 0.

【讨论】:

如何为不可变对象分配对象引用生成副本?你最终不会得到对同一个对象的两个引用吗? 当心,BigDecimal 实际上并不是一成不变的。见***.com/a/12600683/14731 @Gili 尽管使用 BigDecimal,API 本身具有不可变的行为,因此子类很难弄乱基础对象。当然,它可以恶意地重新实现所有内容,并且反思始终是一种可能性。我同意你的意见。但最终类 String 也是如此。并且不要忘记字节码操作、AOP 等。 @deepakl.2000 添加到答案。并稍微修正了代码。您是否过于礼貌而无法指出错误?拜托,我总是用新代码制作它们。 @deepakl.2000 抱歉,忘记复制了。【参考方案3】:

这是您通过传递旧对象并复制其值来创建新对象的地方。

Color copiedColor = new Color(oldColor);

而不是:

Color copiedColor = new Color(oldColor.getRed(),
                              oldColor.getGreen(), oldColor.getBlue());

【讨论】:

这是一个相当简洁的答案。愿意解释为什么会这样做吗?【参考方案4】:

复制构造函数用于使用现有对象的值创建新对象。 一种可能的用例是保护原始对象不被修改,而复制的对象可用于处理。

public class Person  
  
   private String name;  
   private int age;  
   private int height;  


/** 
 * Copy constructor which creates a Person object identical to p.  
 */  
   public person(Person p)  
     
      person = p.person;  
      age = p.age;  
      height = p.height;  
     
.
.
. 

与防御副本有关的here很好读

【讨论】:

【参考方案5】:

需要克隆对象时可以使用java中的复制构造函数

class Copy 
   int a;
   int b;
  public Copy(Copy c1) 
    a=c1.a;
    b=c1.b;
  

在 java 中,当你给 Copy c2=c1;只需创建对原始对象而不是副本的引用,因此您需要手动复制对象值。

看这个:

Why doesn't Java have a copy constructor?

Copy Constructor in Java

【讨论】:

以上是关于复制构造函数和防御性复制的主要内容,如果未能解决你的问题,请参考以下文章

复制构造函数的运用

C++复制构造函数和=号重载问题

为啥我们需要复制构造函数以及何时应该在 java 中使用复制构造函数

C++中复制构造函数被调用的三种情况

为啥隐式复制构造函数调用基类复制构造函数而定义的复制构造函数不调用?

赋值复制构造函数和构造函数 & 异常安全的赋值