来自有效Java的防御拷贝

来自有效Java的防御拷贝,java,design-patterns,Java,Design Patterns,我正在阅读约书亚·布洛赫的《有效Java》,第39项,制作防御性副本,我有一些问题。我始终使用以下构造: MyObject.getSomeRef().setSomething(somevalue); 它的缩写是: SomeRef s = MyClass.getSomeRef(); s.setSomething(); MyObject.setSomeRef(s); 它总是有效的,但是我想如果我的getSomeRef()返回了一个副本,那么我的快捷方式就不起作用了,如果使用快捷方式安全与否,我怎

我正在阅读约书亚·布洛赫的《有效Java》,第39项,制作防御性副本,我有一些问题。我始终使用以下构造:

MyObject.getSomeRef().setSomething(somevalue);
它的缩写是:

SomeRef s = MyClass.getSomeRef();
s.setSomething();
MyObject.setSomeRef(s);
它总是有效的,但是我想如果我的
getSomeRef()
返回了一个副本,那么我的快捷方式就不起作用了,如果使用快捷方式安全与否,我怎么知道
MyObject
的实现是否隐藏?

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

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

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

例如,下面是用于
列表中两个方法的Javadocs,一个方法的结果不能用于更改
列表,另一个方法的结果可以更改
列表:

toArray()

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

子列表()

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


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

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

  • 不要和陌生人说话
  • 封装
请注意,这些规则只是规则,它们有时可能被打破,甚至必须被打破

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

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

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

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

防御性副本是个好主意,但您需要了解何时何地使用它。如果您正在操作的对象网络是内部的,并且它不打算是线程安全的,那么防御性复制是错误应用的

另一方面,如果这是一个公开的对象网络,那么您就有可能违反。如果是,请考虑在<代码> MyObjult< /C> >中使用机械手API。


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

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

  • 如果可以安全地修改某个对象,并将更改存储到基础列表中,那么它应该返回一个可变对象。
  • 如果对象的接收者不能使用它来修改集合,但是将来对集合的操作可能会影响对象,那么它应该返回一个readableThing。
  • 如果它能保证所讨论的对象永远不会改变,那么它应该返回一个不可变的东西。
  • 如果方法的预期结果是调用方拥有一个可变对象,该可变对象使用集合中的数据进行初始化,但未附加到集合中,我建议使用从调用方接受可变对象并适当设置其字段的方法。请注意,这样的用法会让任何阅读代码的人清楚地知道对象没有附加到集合。还可以有一个助手GetMutableCopyOfThing方法。
    Java在声明性地指明谁“拥有”各种对象方面没有做得更好,这太糟糕了。在GC框架出现之前,跟踪所有对象的所有者是谁是一件很烦人的事情,不管这些对象是可变的还是不可变的。由于不可变对象通常没有自然所有者,因此跟踪不可变对象的所有权是一个主要的难题。然而,一般来说,任何具有可变异状态的对象
    Foo
    都应该有一个所有者,该所有者将
    Foo
    状态的可变方面视为其自身状态的一部分。例如,
    ArrayList
    是保存列表项的数组的所有者。如果不知道谁拥有可变对象(或至少是可变对象的可变方面),就不可能使用可变对象编写无bug程序。

    虽然逻辑正确,但这对@Loner没有帮助。他基本上是问他是否应该避免速记形式。这个解决方案涉及到更多的代码,这些代码是速记和扩展形式的结合。@DilumRanatunga Awesome。我只是假设他需要找出函数是否返回相同的实例。我不知何故忽略了主要问题。我从这个错误中学到了比回答其他问题更多的东西+但是如果您像本例中那样实现getFoos(),那么调用方如何向集合中添加呢?通过调用
    addFoo()
    方法。
    public void addFoo(Foo foo) {
        this.foos.add(foo);
        someListener.fooAsBeenAdded(foo);
    }