在暴露内部收集物品时是否应使用Iterator或Iterable?

时间:2015-05-12 15:08:14

标签: java arraylist iterator iterable copyonwritearraylist

我有一个包含私有可变数据列表的类。

我需要在以下条件下公开列表项:

  • 列表不应在外部修改;
  • 使用getter函数的开发人员应该很清楚,他们获得的列表无法修改。

应该将哪个getter函数标记为推荐方法?或者你能提供更好的解决方案吗?

save

UPD:这个问题来自关于列表获取器实现的最佳实践的真实代码审查讨论

3 个答案:

答案 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

通过CopyOnWriteArrayListIterator公开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告诉其他开发人员你返回什么样的列表以及为什么......粗心的用户会被警告异常!!