Effective Java的防御性副本

我正在阅读约书亚布洛赫的“有效Java”,第39项制作防御性副本,我有一些问题。 我总是使用以下结构:

MyObject.getSomeRef().setSomething(somevalue); 

这是短的:

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

它总是有效,但我想如果我的getSomeRef()返回一个副本,那么我的快捷方式将无法工作,如果可以安全地使用快捷方式,我怎么知道MyObject的实现是否被隐藏?

你违反了两个OO编程规则:

  • 不要和陌生人说话
  • 封装

请注意,这些规则只是规则,有时它们可​​以甚至必须被破坏。

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

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

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

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

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

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

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

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

toArray()

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

subList()

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

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

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

另一方面,如果这是公开曝光的对象网,那么您将面临违反得墨忒耳法的风险。 如果是这样,请考虑在myObject上公开操纵器API。

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

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

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

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

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

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