我正在阅读Joshua Bloch撰写的“Effective Java”,第39项制作防御性副本,我有一些问题。我总是使用以下结构:
MyObject.getSomeRef().setSomething(somevalue);
简称:
SomeRef s = MyClass.getSomeRef();
s.setSomething();
MyObject.setSomeRef(s);
它始终有效,但我想如果我的getSomeRef()
正在返回副本,那么我的快捷方式将无效,如果可以安全地使用MyObject
,我怎么知道{{1}}的实现是否隐藏是不是快捷方式?
答案 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
接口或类,并从它mutableThing
和immutableThing
接口派生。属性getter应该根据返回的项与列表的关系返回其中一个接口:
这太糟糕了,Java本身并没有更好地表明声明谁“拥有”各种对象。在GC框架出现之前,不得不跟踪谁拥有所有对象,无论它们是否可变,这都很烦人。由于不可变对象通常没有自然所有者,因此跟踪不可变对象的所有权是一个主要的麻烦。但是,通常情况下,任何具有可以变异状态的对象Foo
都应该只有一个所有者将Foo
状态的可变方面视为其自身状态的一部分。例如,ArrayList
是包含列表项的数组的所有者。如果没有跟踪谁拥有它们(或者至少是它们的可变方面),就不太可能使用可变对象来编写无bug程序。