是否总是需要进行防御性拷贝?

8
我有一段代码,它以二分图作为输入,并返回一个键为“1”的映射,其值是“集合1中的节点”的列表,以及键为“2”的映射,其值是“集合2中的节点”的列表。现在,这个映射是可变的。理论上,我应该使用防御性复制来返回映射。但是,在这种情况下,真的需要吗?这似乎过度了。
class BiPartite {

   Graph graph;
   Map bipartite

   Bipartite(graph) {
      this.graph = graph;
   }

   void calcBipartite() {
     // calculate map 
   }

   Map getMap() {
     // should i make defensive copy ? Appears overkill. 
   }  
}

如果您返回的值是在运行时计算出来的派生信息,那么就不需要进行防御性复制。您是否每次调用方法时都要计算getMap的返回值? - HariKrishnan
4
请注意,如果您没有复制可变对象Graph,则调用者已经可以访问此对象的内部值。 - jpmc26
6个回答

6

这取决于具体情况 :)

  • 您可以相应地记录您的类,并指定返回的Map是BiPartite对象可变状态的可变直接视图。但我不建议这样做。
  • 您可以在getMap()中使用Collections.unmodifiableMap(map)包装内部Map,并记录它反映了BiPartite对象的可变状态,这可以是一个有效的方法,只要BiPartite对象不需要线程安全。如果客户端希望将返回的Map保留为稳定的快照,则可以自行复制。如果不需要这样做,则可以受益于快速包装操作。
  • 您始终可以返回完整副本。如果确实使BiPartite对象线程安全,这将是最有意义的。在这种情况下,您还必须同步所有内部地图操作(包括地图复制操作!)。

基本上,它归结为:考虑BiPartite类及其方法应如何使用,选择适当的实现,并清楚地记录类的行为和背后的原因。


5

是的,你应该返回一个防御性拷贝。如果你担心资源使用,你可以返回一个私有 map 的不可修改视图 Map:

return Collections.unmodifiableMap(bipartite);

是的,那就是我想表达的意思。 - Joni
请注意,Collections.unmodifiableMap() 不是副本 - 它只是一个禁用所有写操作的视图。阅读(原始)答案很容易混淆这两个概念。 - creinig
1
在我看来,不可变视图比复制更好的选择。这样可以将性能考虑留给调用者;他们可以决定是否需要花费时间和空间来创建副本。如果不需要,它们基本上就拥有了一个廉价的副本。 - jpmc26
2
@jpmc26 我不同意大多数情况下的观点,因为对基础集合的更改会影响调用者。这可能有时是有意的,但在大多数情况下可能不是。当您知道不再想更改基础集合时,最好使用副本而不是视图。 - Fabian Barney

2

在解释模式和良好的编程实践时,"ALWAYS"这个词很少是正确的。这完全取决于上下文。

在您的情况下,BiPartite#getMap方法以及类本身都是"包私有的",因此客户端(您的代码用户)将无法直接使用它。如果您知道您从不在该包的边界之外存储或返回该映射,则可以非常安全地说您不需要进行防御性复制。


1

是的,你应该这样做, 否则客户端可以更改您类的私有字段,使您的类行为不正确。


1
这取决于您代码的约定。我遵循复制您想要保留的任何内容的约定。也就是说,这是调用者的责任。
这种方法更加高效,但如果您不知道调用者是否会遵循这种约定,则不够健壮。

0

在我看来,防御性拷贝是一件坏事,因为我遇到的大多数Java框架都希望getter始终返回相同的值,并且快速执行。

如果您想避免代码的不良客户端行为,则应仅公开方法calcBipartite()并让其返回新计算的Map。使用该方法的客户端必须决定如何使用创建的对象以及调用calcBipartite()的次数。

如果您是代码的唯一客户端,则既不应复制也不应包装。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接