我的一个类有一个包含Set的字段。此字段仅在构造函数中填充,然后由其他类读取。最初我有这样的事情:
public class Foo {
public final Set<String> myItems;
public Foo(Collection<String> theirItems) {
this.myItems = new LinkedHashSet<String>(theirItems);
}
}
但这违反了OO最佳实践,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。
在这种情况下你做了什么 - 不惜一切代价保护隐私,接受原始结构的转换/复制,或者牺牲对集合内容的控制权并依赖负责任的呼叫者?
答案 0 :(得分:12)
将不可修改的视图返回到您的集合中:
public Set<String> getItems() {
return Collections.unmodifiableSet(myItems);
}
请注意,这意味着如果调用者仍然会看到你对该集合所做的任何更改,如果他们挂起到返回的集合上。如果你不想那样,你将 来制作副本......没有(简单的)方法。 (理论上,你可以制作一个不可修改的副本并返回对同一副本的引用,直到你下次进行更改,但这会变得混乱。)
重要的一点是记录您选择的任何内容,以便呼叫者不会得到任何令人讨厌的意外。在许多方面,我认为在大多数应用程序中实际上是最重要的事情,其中调用者实际上并不是恶意的。只要很清楚效果会是什么,在大多数情况下防守并不是那么重要。当然,如果您的来电者可能是一些不值得信任的代码而您的设置对安全性至关重要,那么您处于不同的情况。
答案 1 :(得分:8)
这取决于具体情况。有几个选择:
unmodifiableXXX
类的Collections
个工厂。调用者无法修改集合,但调用者可以看到对集合的更新。这通常相对便宜,因为没有分配元素存储;只创建了一个包装器。答案 2 :(得分:3)
你需要多么“安全”?您返回的数组引用了您的集合所包含的相同对象,如果这些对象具有setter,那么您允许调用者修改集合的内容......是吗?
可以克隆该集,或提供用于访问集的不可变接口。这是一个判断电话。如果我正在开发框架代码,我会倾向于在安全和克隆方面犯错误。当客户端更加紧密耦合时,我倾向于不那么保守,比如同一个包中的相关类。
答案 3 :(得分:1)
我会选择你的最后一个选择:
public Set<String> getItems() {
return new LinkedHashSet<String>(myItems);
}
答案 4 :(得分:1)
我要么制作一个集合的副本,要么我使用Collections.unmodifiableSet()。
如果性能有问题,我会破坏封装规则并返回原始集。
答案 5 :(得分:0)
我会克隆该集合以返回它,这样如果调用者修改了集合,它就不会影响你自己的集合。
答案 6 :(得分:0)
作为Collections.unmodifiableSet(java.util.Set)的替代方案,名为Google Collections Library的项目具有 com.google.common.collect.ImmutableSet