当前位置: 首页 > 知识库问答 >
问题:

公开内部集合项时应该使用迭代器或迭代器吗?

阎善
2023-03-14

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

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

  • 列表不应在外部可修改;
  • 对于使用 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();
    }
}

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

共有3个答案

颛孙炜
2023-03-14

通过封装规则,您必须始终返回一个不可修改的列表,在本例中是一个设计规则,因此返回集合。unmodifiableCollection,您不需要将该方法命名为getUnmodifiable,使用getter命名约定并使用Javadoc告诉其他开发人员您返回的列表类型以及原因…粗心的用户将收到异常警报!!

敖子安
2023-03-14

通常,迭代器仅与迭代对象一起使用,用于 for-每个循环。看到一个非迭代类型包含一个返回迭代器的方法会很奇怪,并且可能会让用户感到不安,因为它不能在每个循环中使用。

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

如果您想跳上Java8旅行车,返回Stream可能是一种更“现代”的方法。

鲁烨熠
2023-03-14

“最佳”解决方案实际上取决于预期的应用程序模式(而不是“意见”,正如一位势均力敌的投票者所建议的)。每个可能的解决方案都有可以客观判断的优点和缺点(并且必须由开发人员来判断)。

编辑:已经有一个问题“我应该返回集合还是流?”,Brian Goetz给出了详细的答案。在做出任何决定之前,您也应该参考这些答案。我的答案不是指流,而是指将数据公开为集合的不同方式,指出不同方法的优缺点和含义。

返回迭代器

只返回迭代器是不方便的,无论进一步的细节如何,例如它是否允许修改。单独的迭代器不能在foreach循环中使用。所以客户端必须编写

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

而基本上所有其他解决方案都允许他们只写

for (String s : data.getUnmodifiableIterable()) {
    process(s);
}

公开< code > Collections.unmodifiable...查看内部数据:

您可以公开内部数据结构,并将其包装到相应的 Collections.unmodifiable... 集合中。任何修改返回的集合的尝试都将导致引发“不支持的操作异常”,明确指出客户端不应修改数据。

这里设计空间的一个自由度是你是否隐藏额外的信息:当你有一个< code>List时,你可以提供一个方法

private List<String> internalData;

List<String> getData() {
    return Collections.unmodifiableList(internalData);
}

或者,您可以对内部数据的类型不太具体:

  • 如果调用者应该无法使用List#get(int index)方法进行索引访问,那么您可以将此方法的返回类型更改为Collection

还要考虑,在公开不太特定的接口时,您可以选择将内部数据的类型更改为<code>集

公开内部数据的副本:

一个非常简单的解决方案是只返回列表的副本:

private List<String> internalData;

List<String> getData() {
    return new ArrayList<String>(internalData);
}

这可能具有(可能很大且频繁的)内存副本的缺点,因此只有在集合“小”时才应考虑。

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

公开 CopyOnWriteArrayList

通过< code>Iterator或作为< code>Iterable来公开< code > CopyOnWriteArrayList 可能不是一个好主意:调用方可以选择通过< code>Iterator#remove调用来修改它,而您显然希望避免这种情况。

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

一般考虑

无论如何,你应该虔诚地记录这种行为。特别是,您应该记录调用方不应以任何方式更改返回的数据(无论是否可能不导致异常)。

除此之外,还有一个令人不安的权衡:您可以在留档中精确,或者避免在留档中暴露实现细节。

考虑以下情况:

/**
 * 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();
}

此循环将因< code > 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);

如果返回了内部数据的真实副本,或者通过使用CopyOnWriteArrayList(每个都包含在Collection.unmodifiableList中),则可以避免像ConcurrentModificationException这样的事情。在这方面,这将是“最安全”的解决方案:

  • 调用者无法修改返回的列表
  • 调用者不能直接修改内部状态
  • 如果调用者间接修改了内部状态,那么迭代仍然有效

但是,人们必须考虑各个应用案例是否真的需要如此多的“安全性”,以及如何以一种仍然允许更改内部实现细节的方式记录下来。

 类似资料:
  • 嗨,伙计们,我把这个作为面试问题来回答,但我遇到了麻烦。我熟悉泛型/集合 问题是:所提供的工作区中包含cocI,它是一个类的开始,该类实现了一个迭代器,可用于迭代集合集合。集合集合被传递到类的构造函数中。迭代器应该首先遍历内容深度。 例如,如果集合集合如下所示: 然后迭代器应按以下顺序返回内容:“A”、“B”、“C”、“D”、“E”、“F” Q.在cocI中提供hasNext()和next()方法

  • Iterator(迭代器)是一个接口,它的作用就是遍历容器的所有元素,也是 Java 集合框架的成员,但它与 Collection 和 Map 系列的集合不一样,Collection 和 Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历(即迭代访问)Collection 集合中的元素。 Iterator 接口隐藏了各种 Collection 实现类的底层细节,向应用程序提

  • 问题内容: 我了解像Hashtable这样的集合是同步的,但是有人可以向我解释它是 如何 工作的,在什么时候访问仅限于并​​发调用?例如,假设我使用了一些像这样的迭代器: 有人可以解释一下从不同线程中随机调用这些函数是否有陷阱吗?特别是,迭代器如何进行同步,尤其是在使用entrySet()时,似乎也需要同步?如果在循环之一进行时调用clear()会发生什么?如果removesomething()删

  • 迭代器 乍看来,迭代器似乎很直观。但凑近了看,你会发现标准STL容器提供了四种不同的迭代器:iterator、const_iterator、reverse_iterator和const_reverse_iterator。很快你会注意到在这四种类型中,容器的insert和erase的某些形式只接受其中一种。那是问题的开始。为什么有四种迭代器?它们之间的关系是什么?它们可以互相转化吗?在调用算法和ST

  • For freedom Christ has set us free. Stand firm, therefore, and do not submit again to a yoke of slavery. 基督释放了我们,叫我们得以自由,所以要站立得稳,不要再被奴仆的轭挟制。(GALATIANS 5:1) 迭代器 迭代,对于读者已经不陌生了,曾有专门一节来讲述,如果印象不深,请复习《迭代》。

  • 在Rust中,迭代器共分为三个部分:迭代器、适配器、消费者。 其中,迭代器本身提供了一个惰性的序列,适配器对这个序列进行诸如筛选、拼接、转换查找等操作,消费者则在前两者的基础上生成最后的数值集合。 但是,孤立的看这三者其实是没有意义的,因此,本章将在一个大节里联系写出三者。 迭代器、适配器、消费者