“最佳”解决方案实际上取决于预期的应用程序模式(而不是“意见”,正如密切投票者所建议的那样)。每个可能的解决方案都有可以客观判断的优缺点(并且必须由开发人员判断)。
编辑:已经有一个问题“我应该返回 Collection 还是 Stream? ”,Brian Goetz 给出了详尽的回答。在做出任何决定之前,您也应该查阅此答案。我的回答不是针对流,而只是针对将数据公开为集合的不同方式,并指出不同方法的优缺点和影响。
返回一个迭代器
只返回一个Iterator
是不方便的,不管更多细节,例如它是否允许修改。Iterator
不能单独在foreach
循环中使用。所以客户必须写
Iterator<String> it = data.getUnmodifiableIterator();
while (it.hasNext()) {
String s = it.next();
process(s);
}
而基本上所有其他解决方案都允许他们只写
for (String s : data.getUnmodifiableIterable()) {
process(s);
}
公开Collections.unmodifiable...
内部数据的视图:
您可以公开内部数据结构,包装到相应的Collections.unmodifiable...
集合中。任何修改返回集合的尝试都将导致UnsupportedOperationException
抛出一个,明确指出客户端不应修改数据。
这里设计空间的一个自由度是你是否隐藏附加信息:当你有一个List
时,你可以提供一个方法
private List<String> internalData;
List<String> getData() {
return Collections.unmodifiableList(internalData);
}
或者,您可以不太具体地了解内部数据的类型:
- 如果调用者不能使用该
List#get(int index)
方法进行索引访问,那么您可以将此方法的返回类型更改为Collection<String>
.
- 如果调用者还不能通过调用 获得返回序列的大小
Collection'size()
,那么您可以返回一个Iterable<String>
.
还要考虑到,在公开不太具体的接口时,您稍后可以选择将内部数据的类型更改为 a Set<String>
,例如。如果您保证返回 a List<String>
,那么稍后更改它可能会引起一些麻烦。
公开内部数据的副本:
一个非常简单的解决方案是只返回列表的副本:
private List<String> internalData;
List<String> getData() {
return new ArrayList<String>(internalData);
}
这可能具有(可能大且频繁)内存副本的缺点,因此仅应在集合“小”时考虑。
此外,调用者将能够修改列表,并且他可能希望更改反映在内部状态中(事实并非如此)。这个问题可以通过另外将新列表包装到一个Collections.unmodifiableList
.
暴露一个CopyOnWriteArrayList
CopyOnWriteArrayList
通过其Iterator
或作为 an公开 aIterable
可能不是一个好主意:调用者可以选择通过Iterator#remove
调用来修改它,而您明确希望避免这种情况。
暴露CopyOnWriteArrayList
包装成 a的解决方案Collections.unmodifiableList
可能是一种选择。乍一看,它可能看起来像一个多余的防火墙,但它绝对是合理的——请参阅下一段。
一般注意事项
在任何情况下,您都应该虔诚地记录这种行为。特别是,您应该记录调用者不应该以任何方式更改返回的数据(无论是否可能不会导致异常)。
除此之外,还有一个令人不安的权衡:您可以在文档中保持精确,或者避免在文档中公开实现细节。
考虑以下情况:
/**
* Returns the data. The returned list is unmodifiable.
*/
List<String> getData() {
return Collections.unmodifiableList(internalData);
}
实际上,此处的文档还应说明...
/* ...
* The returned list is a VIEW on the internal data.
* Changes in the internal data will be visible in
* the returned list.
*/
考虑到线程安全和迭代期间的行为,这可能是一个重要信息。考虑一个循环遍历内部数据的不可修改视图。并考虑在这个循环中,有人调用了一个导致内部数据修改的函数:
for (String s : data.getData()) {
...
data.changeInternalData();
}
此循环将中断 a ConcurrentModificationException
,因为内部数据在迭代时被修改。
此处有关文档的权衡是指,一旦指定了某种行为,客户端将依赖此行为。想象一下客户这样做:
List<String> list = data.getList();
int oldSize = list.size();
data.insertElementToInternalData();
// Here, the client relies on the fact that he received
// a VIEW on the internal data:
int newSize = list.size();
assertTrue(newSize == oldSize+1);
ConcurrentModificationException
如果返回了内部数据的真实副本,或者使用 a CopyOnWriteArrayList
(每个都包含在 a中),则可以避免类似的事情Collections.unmodifiableList
。在这方面,这将是“最安全”的解决方案:
- 调用者不能修改返回的列表
- 调用者不能直接修改内部状态
- 如果调用者间接修改了内部状态,那么迭代仍然有效。
但是人们必须考虑是否真的需要对相应的应用案例进行如此多的“安全性”,以及如何以一种仍然允许更改内部实现细节的方式记录这一点。