来自 Effective Java 的防御性副本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9743513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 07:01:41  来源:igfitidea点击:

Defensive copy from Effective Java

javadesign-patterns

提问by Loner shushman

I am reading "Effective Java" by Joshua Bloch, item 39 make defensive copy, and I have some questions. I always use the following construct:

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

MyObject.getSomeRef().setSomething(somevalue);

which is short for:

这是以下的缩写:

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

It always works, but I guess if my getSomeRef()was returning a copy then my shortcut would not work, how can I know if the implementation of MyObjectis hidden if it is safe to use a shortcut or not?

它总是有效,但我想如果我getSomeRef()正在返回一个副本,那么我的快捷方式将不起作用,MyObject如果使用快捷方式是否安全,我怎么知道是否隐藏了实现?

采纳答案by JB Nizet

You're violating two rules of OO programming:

您违反了面向对象编程的两条规则:

  • do not talk to strangers
  • encapsulation
  • 不要和陌生人说话
  • 封装

Note that these rules are just rules, and that they can, or even must be broken sometimes.

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

But if some data is owned by an object, and the object is supposed to guarantee some invariants on the objects it owns, then it should not expose its mutable internal data structures to the outside. Hence the need for a defensive copy.

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

Another often used idiom is to return unmodifiable views of the mutable data structures:

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

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

This idiom, or the defensive copy idiom, can be important, for example, if you must make sure that every modification to the list goes through the object:

这个习惯用法或防御性复制习惯用法很重要,例如,如果您必须确保对列表的每次修改都通过对象:

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

If you don't make a defensive copy or return an unmodifiable view of the list, a caller could add a foo to the list directly, and the listener would not be called.

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

回答by JProgrammer

call getSomeRef() two times and compare there reference if they are different then function is returning the copy else it is returning the same instance.

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

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

回答by Mark Peters

Documentation is the way you (should) know. MyObjectshould document whether the things it exposes can or should be used to modify MyObjectitself. You should only ever modify an object in the ways explicitly granted by the class.

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

For example, here are the Javadocs for two methods in List, one whose result can't be used to change the List, and one whose result can change the List:

例如,这里是 中两种方法的 Javadoc List,一种的结果不能用于改变List,另一种的结果可以改变List

toArray():

toArray()

The returned array will be "safe" in that no references to it are maintained by this list. (In other words, this method must allocate a new array even if this list is backed by an array). The caller is thus free to modify the returned array.

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

subList():

subList()

The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa. The returned list supports all of the optional list operations supported by this list.

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

I would say that silence from the documentation means that you shouldn't use it to mutate the object (use it only for read-only purposes).

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

回答by Dilum Ranatunga

Defensive copy is a good idea, but you need to understand when and where it gets used. If the web of objects you are manipulating is internal, and it not intended to be thread safe, then the defensive copying is being mis-applied.

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

On the other hand, if this is web of objects that are getting exposed publicly, then you are at risk of violating Law of Demeter. If so, consider exposing a manipulator API on your myObject.

另一方面,如果这是公开暴露的对象网络,那么您就有违反德米特法则的风险。如果是这样,请考虑在您的myObject.

As a sidenote, your code sample made the getSomeReflook like a static API. I would suggest that you name any static API that returns a copy of some singleton accordingly (e.g. copyOfSomething()). Similarly for a static factory method.

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

回答by supercat

I would suggest defining a readableThinginterface or class, and deriving from it mutableThingand immutableThinginterfaces. A property getter should return one of those interfaces based upon the returned item's relation to the list:

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

  1. It should return a mutableThing if the thing may be safely modified in such a fashion that changes will be stored to the underlying list.
  2. It should return an readableThing if the recipient of the object cannot use it to modify the collection, but there's a possibility that future operations with the collection might affect the object.
  3. It should return an immutableThing if it can guarantee that the object in question will never change.
  4. If the intended outcome of the method is for the caller to have a mutable thing which is initialized with data from the collection, but which is not attached ot it, I would suggest having the method which accepts a mutableThing from the caller and sets up its fields appropriately. Note that such usage would make clear to anyone reading the code that the object was not attached to the collection. One could also have a helper GetMutableCopyOfThing method.
  1. 如果可以以将更改存储到底层列表的方式安全地修改事物,则它应该返回一个 mutableThing。
  2. 如果对象的接收者不能使用它来修改集合,它应该返回一个 readableThing,但有可能将来对集合的操作可能会影响对象。
  3. 如果它可以保证所讨论的对象永远不会改变,它应该返回一个 immutableThing。
  4. 如果该方法的预期结果是调用者拥有一个可变事物,该事物使用集合中的数据进行初始化,但未附加到它,我建议使用该方法接受来自调用者的 mutableThing 并设置其适当的字段。请注意,这种用法会使任何阅读代码的人都清楚该对象未附加到集合中。还可以有一个助手 GetMutableCopyOfThing 方法。

It's too bad Java doesn't inherently do a better job of indicating declaratively who "owns" various objects. Before the days of GC frameworks, it was annoying having to keep track of who owned all objects, whether they were mutable or not. Since immutable objects often have no natural owner, tracking ownership of immutable objects was a major hassle. Generally, however, any object Foowith state that can be mutated should have exactly one owner which regards mutable aspects of Foo's state as being parts of its own state. For example, an ArrayListis the owner of an array which holds the list items. One is unlikely to write bug-free programs using mutable objects if one doesn't keep track of who owns them (or at least their mutable aspects).

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