3

我的一个类有一个包含 Set 的字段。该字段仅在构造函数中填充,然后由其他类读取。最初我有这样的事情:

public class Foo {
    public final Set<String> myItems;
    public Foo(Collection<String> theirItems) {
        this.myItems = new LinkedHashSet<String>(theirItems);
    }
}

但这违背了面向对象的最佳实践,myItems 应该是私有的,并且只能通过 setter 和 getter 访问。于是我把它改成了:

public class Foo {
    private final Set<String> myItems;
    public Foo(Collection<String> theirItems) {
        this.myItems = new LinkedHashSet<String>(theirItems);
    }
    public Set<String> getItems() {
        return myItems;
    }
}

现在 myItems 是私有的,但是调用 getItems() 的人仍然可以随意添加/删除项目,这与我之前的情况基本相同。(我实际上并不担心有人更改项目内容,这更像是一个理论问题)

于是我改变了 getItems() 以返回一个数组:

public String[] getItems() {
    return myItems.toArray(new String[myItems.size()]);
}

现在我的物品真的很私密。不幸的是,我知道将读取项目的对象实际上想要使用 Set,因此它必须立即将数组转换回来。我还可以返回 myItems 的副本:

public Set<String> getItems() {
    return new LinkedHashSet<String>(myItems);
}

这为调用者提供了他们想要的东西,但在每次访问时都会创建一个新的 Set。

在这种情况下你会怎么做——不惜一切代价保护隐私,接受原始结构的转换/复制,或者牺牲对集合内容的控制并依赖负责任的调用者?

4

7 回答 7

12

返回一个不可修改的视图到你的集合上:

public Set<String> getItems() {
    return Collections.unmodifiableSet(myItems);
}

请注意,这意味着调用者仍然会看到对集合所做的任何更改,如果他们挂在返回的集合上。如果你不想要那个,你将不得不制作一个副本......没有(简单的)方法可以解决这个问题。(理论上,您可以制作一个不可修改的副本并返回对同一副本的引用,直到您下次进行更改,但这会变得混乱。)

重要的一点是记录您选择的任何内容,以便调用者不会得到任何令人讨厌的惊喜。在许多方面,我认为这实际上是大多数应用程序中重要的事情,调用者实际上并不是恶意的。只要很清楚会产生什么影响,在大多数情况下,防守并不那么重要。当然,如果您的调用者可能是一些不可信的代码,而您的集合对安全等至关重要,那么您的情况就不同了。

于 2009-10-01T05:27:53.003 回答
8

这取决于上下文。有几个选项:

  1. 返回集合。调用者可以根据需要修改集合,但不能分配新集合。这是最便宜的,但提供的保护最少。
  2. 返回一个视图(使用类unmodifiableXXX的工厂Collections)。调用者无法修改集合,但对集合的更新将对调用者可见。这通常相对便宜,因为没有分配元素存储;只创建一个包装器。
  3. 使用适当集合的复制构造函数返回集合的快照。在这里,调用者得到一个集合的副本。他们可以修改副本,但不会更新原始副本,并且对原始副本的更新在副本中不可见。这是最贵的。
于 2009-10-01T05:28:34.750 回答
3

你需要有多“安全”?您返回的数组引用了您的集合所拥有的相同对象,如果这些对象具有设置器,那么您允许调用者修改您的集合的内容......可以吗?

可以克隆该集合,或提供一个不可变的接口来访问该集合。这是一个判断电话。如果我正在开发框架代码,我会倾向于在安全和克隆方面犯错。当客户端更紧密耦合时,我倾向于不那么保守,例如,同一包中的相关类。

于 2009-10-01T05:31:02.310 回答
1

我会选择你的最后一个选择:

public Set<String> getItems() {
    return new LinkedHashSet<String>(myItems);
}
于 2009-10-01T05:27:14.143 回答
1

我要么制作集合的副本,要么使用 Collections.unmodifiableSet()。

如果性能是一个问题,我会打破封装规则并返回原始集合。

于 2009-10-01T05:30:47.803 回答
0

我会克隆该集合以返回它,这样如果调用者修改了该集合,它就不会影响您自己的集合。

于 2009-10-01T05:22:14.810 回答
0

作为 Collections.unmodifiableSet(java.util.Set) 的替代方案,名为Google Collections Library的项目具有 com.google.common.collect.ImmutableSet

于 2009-10-01T06:48:02.187 回答