【问题标题】:Can a iterator change the collection it is iterating over? Java迭代器可以更改它正在迭代的集合吗?爪哇
【发布时间】:2014-01-31 14:38:50
【问题描述】:

我正在尝试使用迭代器的迭代次数作为计数器,但想知道这样做的后果。

private int length(Iterator<?> it) {
    int i = 0;

    while(it.hasNext()) {
        it.next();
        i++;
    }

    return i;
}

这很好用,但我担心迭代器可能会在幕后做些什么。也许当我遍历堆栈时,它会将项目从堆栈中弹出,或者如果我正在使用优先级队列,它会修改优先级。

javadoc 是这样说迭代器的:

下一个
E 下一个()
返回迭代中的下一个元素。
退货:
迭代中的下一个元素
抛出:
NoSuchElementException - 如果迭代没有更多元素

我无法保证遍历这个未知集合不会修改它。我是否在考虑不切实际的边缘情况,或者这是一个问题?有没有更好的办法?

【问题讨论】:

  • 如果有人给你一个Iterator,他们希望你打电话给next(),不管有什么副作用。

标签: java iterator


【解决方案1】:

Iterator 只是为某种流提供了一个接口,因此next() 不仅完全有可能以某种方式销毁数据,而且Iterator 中的数据甚至可能是唯一的并且不可替代。

我们可以想出更直接的例子,但一个简单的例子是DirectoryStream 中的Iterator。虽然DirectoryStream 在技术上是Iterable,但它只允许构造一个Iterator,所以如果您尝试执行以下操作:

Path dir = ...
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
  int count = length(stream.iterator());
  for (Path entry: stream) {
    ...
  }
}

您会在 foreach 块中遇到异常,因为流只能迭代一次。所以总而言之,您的length() 方法可能会更改对象并丢失数据。

此外,Iterator 没有理由必须与某个单独的数据存储关联。以answer I gave a few months 前为例,它提供了一种干净的方式来选择n 随机数。通过使用无限Iterator,我们能够懒惰地提供、过滤和传递任意大量的随机数据,无需一次存储所有数据,甚至在需要时计算它们。因为Iterator不支持任何数据结构,查询它显然是破坏性的。

话虽如此,这些示例不会使您的方法变差。请注意,Guava library(每个人都应该使用)提供了一个 Iterators 类,该类具有您在上面详述的行为,称为 size() 以符合集合框架。然后,此类方法的用户有责任了解他们正在使用哪种数据,并避免进行粗心的调用,例如尝试计算他们知道无法替换的Iterator 中的结果数量。

【讨论】:

    【解决方案2】:

    据我所知,Collection 规范没有明确声明迭代集合不会修改它,但标准库中没有类显示该行为(实际上至少有一个,请参阅dimo414's answer),所以任何这样做的课程都会受到高度怀疑。我认为您不必担心这一点。

    请注意,Guava 库以与您相同的方式实现 Iterators.size()Iterables.size(),因此很明显它们在一般情况下认为它是安全的。

    【讨论】:

    • +1。请注意,即使java.util.Iterator 确实指定了类似的内容,它实际上也不是绑定要求。一个类实际上并不符合其接口的约定的情况经常发生。 (例如,JDK 的java.util.IdentityHashMap 完全、有意并有据可查地违反了java.util.Map 的一般合同。)
    • Collection 的子对象以外的对象可以拥有或成为Iterators。
    【解决方案3】:

    不,迭代集合不会修改集合。 Iterator 类确实有一个 remove() 方法,这是在迭代期间从集合中删除元素的唯一安全方法。但简单地调用hasNext()next() 不会修改集合。

    请记住,如果您修改 next() 返回的对象,这些更改将出现在您的集合中。

    【讨论】:

    • 这是一个合理的行为,但是在迭代时会被“修改”的集合仍然符合迭代合同(显然),这就是问题所在。
    • 迭代集合(集合框架中的一些东西)不会修改集合,但这并不意味着某些任意定义的IteratorIterable 如果选择这样做就不能这样做。考虑一个返回随机数的Iterator;一旦next() 被调用,该号码实际上就“消失”了。
    【解决方案4】:

    想一想——返回事物的方法是(如果写得正确的话)访问器方法,这意味着它们只是返回数据。他们不会修改它(它们不是 mutator 方法)。

    这是我磁盘上的一个示例,说明如何实现迭代器。如您所见,实际上没有修改任何值。

    public class ArraySetIterator implements Iterator
    {
        private int nextIndex;
        private ArraySet theArraySet;
    
        public ArraySetIterator (ArraySet a)
        {
            this.nextIndex = 0;
            this.theArraySet = a;
        }
    
        public boolean hasNext ()
        {
            return this.nextIndex < this.theArraySet.size();
        }
    
        public Object next()
        {
            return this.theArraySet.get(this.nextIndex++);
        }
    }
    

    【讨论】:

    • 是的一般 Iterator 不会修改。 OP正在询问特殊情况。不修改其数据的示例Iterator 是无关紧要的。没有固定的规则,返回值的方法永远不会改变状态。
    猜你喜欢
    • 2017-08-04
    • 2015-09-03
    • 2010-09-16
    • 1970-01-01
    • 2019-02-14
    • 2012-06-25
    • 2018-02-16
    • 2016-11-29
    • 2011-01-03
    相关资源
    最近更新 更多