我有一个包含私有可变数据列表的类。
我需要在以下条件下公开列表项:
应该将哪个getter函数标记为推荐方法?或者你能提供更好的解决方案吗?
save
UPD:这个问题来自关于列表获取器实现的最佳实践的真实代码审查讨论
答案 0 :(得分:7)
" best"解决方案实际上取决于预期的应用模式(而不是关于"意见",如关闭选民的建议)。每种可能的解决方案都有可以客观判断的优点和缺点(有由开发人员判断)。
编辑:已经有一个问题" Should I return a Collection or a 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()
获取返回序列的 size ,则可以返回Iterable<String>
。 另外考虑一下,在公开不太具体的接口时,您可以选择将内部数据的类型更改为Set<String>
。如果您保证返回List<String>
,那么稍后更改此信息可能会引起一些麻烦。
公开内部数据的副本:
一个非常简单的解决方案是只返回列表的副本:
private List<String> internalData;
List<String> getData() {
return new ArrayList<String>(internalData);
}
这可能具有(可能是大而频繁的)内存副本的缺点,因此只应在集合“小”时才考虑。
此外,调用者将能够修改列表,并且可能期望更改反映在内部状态(不是这种情况)。通过将 new 列表另外包装到Collections.unmodifiableList
中可以缓解此问题。
公开CopyOnWriteArrayList
通过CopyOnWriteArrayList
或Iterator
公开Iterable
可能不是一个好主意:来电者可以选择通过Iterator#remove
来电来修改它,而你显然想避免这种情况。
将包含在CopyOnWriteArrayList
中的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();
}
此循环将以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
(每个包裹在CopyOnWriteArrayList
中),则可以避免Collections.unmodifiableList
之类的内容。这将是最安全的&#34;解决方案,在这方面:
但人们必须考虑是否有这么多&#34;安全&#34;对于相应的应用案例确实是必需的,以及如何以仍允许更改内部实现细节的方式记录它。
答案 1 :(得分:1)
通常,Iterator仅与Iterable一起使用,用于for-each循环。看到非Iterable类型包含一个返回Iterator的方法会很奇怪,它可能会让用户感到不安,因为它不能用于for-each循环。
所以我建议在这种情况下使用Iterable。如果有意义,你甚至可以让你的班级implements Iterable
。
如果你想跳上Java 8旅行车,那么返回Stream
可能是一个更现代的&#34;方法
答案 2 :(得分:0)
通过封装规则,你必须总是返回一个不可修改的列表,在你的情况下是一个设计规则,所以返回Collections.unmodifiableCollection,你不需要将方法命名为getUnmodifiable,使用getter命名方法并使用Javadoc告诉其他开发人员你返回什么样的列表以及为什么......粗心的用户会被警告异常!!