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

22
什么是复制构造函数?
有人能分享一个小例子来帮助理解防御性复制原则吗?

http://www.javapractices.com/topic/TopicAction.do?Id=12 - PermGenError
你可以在这里找到有关防御性拷贝的详细解释:Defensive copying - longhua
5个回答

24

这里有一个很好的例子:

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并复制它 - 这就是一个拷贝构造函数
这是一个防御性复制,因为通过复制它来保护原始的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);

请注意,这不一定是创建对象的正确方式。但是,这是一种好的创建对象的方式,可以确保您永远不会意外地拥有两个对同一对象的引用。显然,如果这正是您想要实现的目标,那么这只是一件好事。

11

在C++中,我们经常看到拷贝构造函数的应用。它们用于部分隐藏的、自动调用的操作。

我想起了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.

1
注意,BigDecimal 实际上并不是不可变的。请参见 https://dev59.com/hXVC5IYBdhLWcg3w7Vtq#12600683。 - Gili
@JoopEggen 所有字段都是私有的事实并不意味着该类是不可变的。恶意子类可以使用反射来读取它们的值。即使没有访问这些字段,它也可能返回不正确的结果或窥探应用程序与对象的交互。 - Gili
1
@Gili 虽然使用 BigDecimal API 本身具有不变行为,因此子类很难搞乱基础对象。当然,它可能会恶意地重新实现所有内容,而反射始终是一种可能性。我同意你的看法。但是 String 这个最终类也是如此。更不要忘记字节码操作、AOP 等等。 - Joop Eggen
1
@deepakl.2000已添加至回答并稍微更正了代码。你是不是太客气了,没有指出错误?别客气,我在编写新代码时也常犯错。 - Joop Eggen
1
@deepakl.2000 抱歉,忘记复制了。 - Joop Eggen
显示剩余15条评论

2

在Java中,拷贝构造函数可以用于克隆对象。

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

在Java中,当你给出Copy c2=c1时,它只是创建了一个对原始对象的引用,而不是副本,因此你需要手动复制对象值。
参考这个:

1
这是创建新对象的位置,通过传递旧对象并复制其值。
Color copiedColor = new Color(oldColor);

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

这是一个相当简洁的答案。能否解释一下为什么会这样做? - Duncan Jones

1

复制构造函数用于使用现有对象的值创建新对象。
可能的一个用例是保护原始对象免受修改,同时可以使用复制的对象进行操作。

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;  
   }  
.
.
. 
}

与防御性拷贝相关的这篇文章是一篇不错的阅读材料


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接