kza*_*kza 10 java iterator iterable arraylist copyonwritearraylist
我有一个包含私有可变数据列表的类.
我需要在以下条件下公开列表项:
应该将哪个getter函数标记为推荐方法?或者你能提供更好的解决方案吗?
class DataProcessor {
private final ArrayList<String> simpleData = new ArrayList<>();
private final CopyOnWriteArrayList<String> copyData = new CopyOnWriteArrayList<>();
public void modifyData() {
...
}
public Iterable<String> getUnmodifiableIterable() {
return Collections.unmodifiableCollection(simpleData);
}
public Iterator<String> getUnmodifiableIterator() {
return Collections.unmodifiableCollection(simpleData).iterator();
}
public Iterable<String> getCopyIterable() {
return copyData;
}
public Iterator<String> getCopyIterator() {
return copyData.iterator();
}
}
Run Code Online (Sandbox Code Playgroud)
UPD:这个问题来自关于列表getter实现的最佳实践的真实代码审查讨论
"最佳"解决方案实际上取决于预期的应用模式(而不是关于"意见",如关闭选民所建议的那样).每种可能的解决方案都有可以客观判断的优点和缺点(并且必须由开发人员进行判断).
编辑:已经有一个问题" 我应该返回一个集合还是一个流? ",Brian Goetz给出了精心解答的答案.在做出任何决定之前,您也应该查阅这些答案.我的回答并不是指流,而只是指将数据暴露为集合的不同方式,指出了不同方法的优缺点和含义.
返回迭代器
只返回一个Iterator是不方便的,无论进一步的细节,例如它是否允许修改.一个Iterator不能单独在使用foreach循环.所以客户必须写
Iterator<String> it = data.getUnmodifiableIterator();
while (it.hasNext()) {
String s = it.next();
process(s);
}
Run Code Online (Sandbox Code Playgroud)
而基本上所有其他解决方案都允许他们只写
for (String s : data.getUnmodifiableIterable()) {
process(s);
}
Run Code Online (Sandbox Code Playgroud)
Collections.unmodifiable...在内部数据上公开视图:
您可以公开内部数据结构,包装到相应的Collections.unmodifiable...集合中.任何修改返回集合的尝试都会导致UnsupportedOperationException抛出,明确指出客户端不应修改数据.
这里设计空间的一个自由度是你是否隐藏了其他信息:当你有一个时List,你可以提供一种方法
private List<String> internalData;
List<String> getData() {
return Collections.unmodifiableList(internalData);
}
Run Code Online (Sandbox Code Playgroud)
或者,您可能不太关注内部数据的类型:
List#get(int index)方法进行索引访问,则可以将此方法的返回类型更改为Collection<String>.Collection'size(),那么你可以返回一个Iterable<String>.还要考虑到,在公开不太具体的接口时,您可以选择将内部数据的类型更改Set<String>为例如.如果您保证返回a List<String>,那么稍后更改它可能会引起一些麻烦.
公开内部数据的副本:
一个非常简单的解决方案是只返回列表的副本:
private List<String> internalData;
List<String> getData() {
return new ArrayList<String>(internalData);
}
Run Code Online (Sandbox Code Playgroud)
这可能具有(可能大且频繁)存储器副本的缺点,因此仅在集合"小"时才应考虑.
此外,调用者将能够修改列表,并且他可能希望更改反映在内部状态(事实并非如此).通过另外将新列表包装到a中可以缓解此问题Collections.unmodifiableList.
暴露一个 CopyOnWriteArrayList
暴露一个CopyOnWriteArrayList通过它Iterator或作为一个Iterable可能不是一个好主意:调用者可以选择通过Iterator#remove调用修改它,你明确想避免这种情况.
暴露CopyOnWriteArrayList包裹成a 的解决方案Collections.unmodifiableList可能是一种选择.乍一看,它可能看起来像一个多余的厚防火墙,但它肯定是合理的 - 见下一段.
一般考虑
无论如何,你应该虔诚地记录这种行为.特别是,您应该记录调用者不应该以任何方式更改返回的数据(无论是否可以在不引起异常的情况下).
除此之外,还有一个令人不安的权衡:您可以在文档中准确,也可以避免在文档中公开实现细节.
考虑以下情况:
/**
* Returns the data. The returned list is unmodifiable.
*/
List<String> getData() {
return Collections.unmodifiableList(internalData);
}
Run Code Online (Sandbox Code Playgroud)
这里的文档实际上也应说明......
/* ...
* The returned list is a VIEW on the internal data.
* Changes in the internal data will be visible in
* the returned list.
*/
Run Code Online (Sandbox Code Playgroud)
考虑到线程安全性和迭代期间的行为,这可能是一个重要信息.考虑一个迭代内部数据的不可修改视图的循环.并且考虑在这个循环中,有人调用一个导致内部数据修改的函数:
for (String s : data.getData()) {
...
data.changeInternalData();
}
Run Code Online (Sandbox Code Playgroud)
这个循环将以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);
Run Code Online (Sandbox Code Playgroud)
ConcurrentModificationException如果返回了内部数据的真实副本,或者使用a CopyOnWriteArrayList(每个包装成a Collections.unmodifiableList),就可以避免类似的事情.在这方面,这将是"最安全"的解决方案:
但是,必须考虑相应的应用案例是否真的需要这么多"安全",以及如何以一种仍然允许更改内部实现细节的方式记录它.