Java 公开内部集合项时是否应使用迭代器或Iterable?

Java 公开内部集合项时是否应使用迭代器或Iterable?,java,arraylist,iterator,iterable,copyonwritearraylist,Java,Arraylist,Iterator,Iterable,Copyonwritearraylist,我有一个带有私有可变数据列表的类 在以下条件下,我需要公开列表项: 列表不能在外部修改 对于使用getter函数的开发人员来说,应该清楚的是,他们得到的列表不能修改 应该将哪个getter函数标记为推荐方法?或者你能提供更好的解决方案吗 class DataProcessor { private final ArrayList<String> simpleData = new ArrayList<>(); private final CopyOnWri

我有一个带有私有可变数据列表的类

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

  • 列表不能在外部修改
  • 对于使用getter函数的开发人员来说,应该清楚的是,他们得到的列表不能修改
应该将哪个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();
    }
}
类数据处理器{
private final ArrayList simpleData=new ArrayList();
private final CopyOnWriteArrayList copyData=新CopyOnWriteArrayList();
公共void modifyData(){
...
}
public Iterable getUnmodifiableIterable(){
返回集合。不可修改集合(simpleData);
}
公共迭代器getUnmodifiableIterator(){
返回Collections.unmodifiableCollection(simpleData.iterator();
}
公共Iterable getCopyIterable(){
返回复制数据;
}
公共迭代器getCopyIterator(){
返回copyData.iterator();
}
}

UPD:这个问题来自于一个真实的代码评审讨论,讨论了通过封装规则实现列表获取器的最佳实践,您必须始终返回一个不可修改的列表,在您的情况下,这是一个设计规则,所以返回集合。不可修改的集合,并且您不需要将该方法命名为getUnmodifiable,使用getter命名约定,并使用Javadoc告诉其他开发人员您返回的是哪种列表以及返回原因…粗心的用户将收到异常警报

通常,迭代器仅用于Iterable,用于for each循环。看到一个不可Iterable类型包含一个返回迭代器的方法会很奇怪,而且它可能会让用户感到不安,因为它不能用于每个循环

因此,我建议在这种情况下使用Iterable。如果有意义的话,您甚至可以让类
实现Iterable

如果你想跳上Java 8的马车,返回一个可能是一种更“现代”的方法。

最好的解决方案实际上取决于预期的应用程序模式(而不是像一位亲密的投票者所建议的那样取决于“意见”)。每个可能的解决方案都有可以客观判断的优点和缺点(并且需要开发人员判断)


编辑:已经有一个问题“”,由布赖恩·戈茨详细回答。在做出任何决定之前,你也应该参考这些答案。我的答案不是指流,而是指将数据作为一个集合公开的不同方式,指出不同方法的优缺点和含义


返回迭代器

不管进一步的细节,例如是否允许修改,只返回一个
迭代器是不方便的。不能在
foreach
循环中单独使用
迭代器。因此,客户必须编写

Iterator<String> it = data.getUnmodifiableIterator();
while (it.hasNext()) {
    String s = it.next();
    process(s);
}

公开
集合。不可修改…
内部数据视图:

您可以公开内部数据结构,将其包装到相应的
集合中。不可修改…
集合。任何修改返回集合的尝试都将导致抛出
UnsupportedOperationException
,明确指出客户端不应修改数据

设计空间中的一个自由度是是否隐藏其他信息:当您有
列表时,您可以提供一种方法

private List<String> internalData;

List<String> getData() {
    return Collections.unmodifiableList(internalData);
}
这可能有(可能较大且频繁)内存拷贝的缺点,因此只有在集合“较小”时才应考虑

此外,调用者将能够修改列表,并且他可能希望更改反映在内部状态中(事实并非如此)。这个问题可以通过将新列表额外包装到集合中来缓解。不可修改列表


公开
CopyOnWriteArrayList

通过其
迭代器
或作为
Iterable
公开
CopyOnWriteArrayList
可能不是一个好主意:调用方可以选择通过
迭代器#remove
调用修改它,而您明确希望避免这种情况

公开包装到集合中的
CopyOnWriteArrayList
的解决方案。不可修改列表
可能是一个选项。乍一看,它可能看起来像一个多余的厚防火墙,但它肯定是合理的——见下一段


一般注意事项

在任何情况下,你都应该虔诚地记录下这种行为。特别是,您应该记录调用者不是以任何方式更改返回数据的(无论是否可能不引起异常)

除此之外,还有一个令人不安的权衡:您可以在文档中精确地描述,也可以避免在文档中暴露实现细节

考虑以下情况:

/**
 * Returns the data. The returned list is unmodifiable. 
 */
List<String> getData() {
    return Collections.unmodifiableList(internalData);
}
考虑到线程安全和迭代期间的行为,这可能是一个重要信息。考虑在内部数据上迭代不可修改视图的循环。并考虑在这个循环中,有人调用一个导致内部数据修改的函数:

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);
List List=data.getList();
int oldSize=list.size();
data.insertElementToInternalData();
//在这里,客户依赖于他收到
//内部数据视图:
int newSize=list.size();
assertTrue(新闻大小)
/* ...
 * 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();
}
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);