Effective Java的防御性副本

时间:2012-03-16 19:41:12

标签: java design-patterns

我正在阅读Joshua Bloch撰写的“Effective Java”,第39项制作防御性副本,我有一些问题。我总是使用以下结构:

MyObject.getSomeRef().setSomething(somevalue);

简称:

SomeRef s = MyClass.getSomeRef();
s.setSomething();
MyObject.setSomeRef(s);

它始终有效,但我想如果我的getSomeRef()正在返回副本,那么我的快捷方式将无效,如果可以安全地使用MyObject,我怎么知道{{1}}的实现是否隐藏是不是快捷方式?

5 个答案:

答案 0 :(得分:26)

你违反了OO编程的两条规则:

  • 不要与陌生人交谈
  • 封装

请注意,这些规则只是规则,有时甚至必须打破这些规则。

但是如果一些数据由一个对象拥有,并且该对象应该保证它拥有的对象上有一些不变量,那么它就不应该将它的可变内部数据结构暴露给外部。因此需要一个防御性的副本。

另一个常用的习惯用法是返回可变数据结构的不可修改的视图:

public List<Foo> getFoos() {
    return Collections.unmodifiableList(this.foos);
}

这个成语或防御性复制成语可能很重要,例如,如果你必须确保列表的每个修改都通过对象:

public void addFoo(Foo foo) {
    this.foos.add(foo);
    someListener.fooAsBeenAdded(foo);
}

如果您没有制作防御性副本或返回列表的不可修改视图,则调用者可以直接向列表添加foo,并且不会调用侦听器。

答案 1 :(得分:10)

文档是您(应该)知道的方式。 MyObject应该记录它所公开的内容是否可以或应该用于修改MyObject本身。 您应该只使用类明确授予的方式修改对象。

例如,以下是List中两个方法的Javadoc,其中一个结果不能用于更改List,另一个结果可以更改List

<强> toArray()

  

返回的数组将是“安全的”,因为此列表不会保留对它的引用。 (换句话说,即使此列表由数组支持,此方法也必须分配新数组)。 调用者可以自由修改返回的数组。

<强> subList()

  

返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。返回的列表支持此列表支持的所有可选列表操作。

我会说文档中的沉默意味着你不应该使用它来改变对象(仅用于只读目的)。

答案 2 :(得分:2)

防御性副本是一个好主意,但您需要了解它的使用时间和地点。如果您正在操作的对象Web是内部的,并且它不是线程安全的,那么防御性复制就会被误用。

另一方面,如果这是公开曝光的对象网络,那么您可能会违反Law of Demeter。如果是这样,请考虑在myObject上公开操纵器API。

作为旁注,您的代码示例使getSomeRef看起来像一个静态API。我建议你命名任何静态API,相应地返回一些单例的副本(例如copyOfSomething())。类似地,对于静态工厂方法。

答案 3 :(得分:1)

调用getSomeRef()两次并比较引用,如果它们不同则函数返回副本,否则返回相同的实例。

if(MyObject.getSomeRef() == MyObject.getSomeRef()){
     // same instance
}else{
     // copied instance
}

答案 4 :(得分:1)

我建议定义一个readableThing接口或类,并从它mutableThingimmutableThing接口派生。属性getter应该根据返回的项与列表的关系返回其中一个接口:

  1. 如果可以安全地修改事物,它将返回mutableThing,以便将更改存储到基础列表中。
  2. 如果对象的收件人不能使用它来修改集合,它应该返回readableThing,但是将来对该集合的操作可能会影响该对象。
  3. 如果可以保证有问题的对象永远不会改变,它应该返回一个immutableThing。
  4. 如果方法的预期结果是调用者有一个可变的东西,它是用集合中的数据初始化的,但是没有附加它,我会建议使用接受mutableThing的方法来自调用者并适当地设置其字段。请注意,此类用法会使读取代码的任何人都清楚该对象未附加到集合中。也可以有一个帮助器GetMutableCopyOfThing方法。

这太糟糕了,Java本身并没有更好地表明声明谁“拥有”各种对象。在GC框架出现之前,不得不跟踪谁拥有所有对象,无论它们是否可变,这都很烦人。由于不可变对象通常没有自然所有者,因此跟踪不可变对象的所有权是一个主要的麻烦。但是,通常情况下,任何具有可以变异状态的对象Foo都应该只有一个所有者将Foo状态的可变方面视为其自身状态的一部分。例如,ArrayList是包含列表项的数组的所有者。如果没有跟踪谁拥有它们(或者至少是它们的可变方面),就不太可能使用可变对象来编写无bug程序。