83

可能重复:
为什么 Java 的 Iterator 不是 Iterable?

在给定迭代器的情况下使用 for-each 循环的惯用方式?

我们可以使用 for-each 循环来迭代 Iterator 类型的对象吗?

据我所知,foreach 循环是 Java 5 中添加的语法糖。所以

Iterable<O> iterable;
for(O o : iterable) {
    // Do something
}

基本上会产生与

Iterable<O> iterable;
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) {
    O o = iter.next();
    // Do something
}

但是,如果我一开始没有可迭代对象,而只有一个迭代器(比如说,因为一个类提供了两个不同的迭代器),我就不能使用语法糖 foreach 循环。显然我仍然可以进行简单的旧样式迭代。但是,我实际上想做:

Iterator<O> iter;
for(O o : iter /* Iterator<O>, not Iterable<O>! */) {
     // Do something
}

当然我可以做一个假的Iterable

class Adapter<O> implements Iterable<O> {
    Iterator<O> iter;

    public Adapter(Iterator<O> iter) {
        this.iter = iter;
    }

    @Override
    public Iterator<O> iterator() {
        return iter;
    }
}

(这实际上是对 Iterable API 的丑陋滥用,因为它只能迭代一次!)

如果它是围绕Iterator而不是可迭代设计的,可以做一些有趣的事情:

for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections

for(O o : list.backwardsIterator()) {} // Or backwards

Iterator<O> iter;
for(O o : iter) {
    if (o.something()) { iter.remove(); }
    if (o.something()) { break; }
}
for(O : iter) { } // Do something with the remaining elements only.

有谁知道为什么语言是这样设计的?为了避免歧义,如果一个类同时实现IteratorIterable?为了避免假定“for(O o : iter)”将处理所有元素两次(并且忘记获取新的迭代器)的程序员错误?或者还有其他原因吗?

还是有一些我不知道的语言技巧?

4

4 回答 4

24

所以我现在有了一个比较合理的解释:

短版:因为该语法也适用于没有迭代器的数组。

如果按照我的建议设计语法Iterator,它将与数组不一致。让我给出三个变体:

A)由 Java 开发人员选择:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }

它的行为方式相同,并且在数组和集合之间高度一致。然而,迭代器必须使用经典的迭代风格(至少不太可能导致错误)。

B)允许数组和Iterators

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

现在数组和集合不一致;但是数组和 ArrayList 密切相关,应该表现相同。现在,如果在任何时候,语言被扩展以使例如数组实现Iterable,它就会变得不一致。

C)允许所有三个:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }

现在,如果我们最终处于不清楚的情况下,当有人同时 Iterable实现and时Iterator(for 循环是否应该获得一个新的迭代器或迭代当前 - 在树状结构中很容易发生!?!)。一个简单的 tie-braker ala “Iterable beats Iterator” 不幸的是不会这样做:它突然引入了运行时与编译时间差异和泛型问题。

现在突然间,我们需要注意是否要迭代集合/可迭代对象或数组,此时我们以大混乱为代价获得了很少的好处。

Java (A) 中的“for each”方式非常一致,它几乎不会导致编程错误,并且它允许将来可能将数组转换为常规对象。

有一个变体D)可能也可以正常工作:for-each 仅适用于迭代器。.iterator()最好通过向原始数组添加方法:

Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

但这需要更改运行时环境,而不仅仅是编译器,并且破坏了向后兼容性。另外,上面提到的混乱仍然存在

Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }

只对数据进行一次迭代。

于 2012-06-26T23:23:27.507 回答
13

Iterable 接口正是为此目的而创建的(增强了 for 循环),如原始 JSR中所述,尽管 Iterator 接口已经在使用中。

关于 JSR 中讨论的新接口(注意包名):

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator(在 JSR 中建议进行改装,java.util.Iterator但实际上并未完成)

…JSR 说:

这些新接口用于防止语言对java.util否则会产生的依赖性。

于 2012-06-26T22:52:20.407 回答
9

因为“for”循环会对迭代器造成破坏。除非实现 ListIterator 子接口,否则无法重置迭代器(即移回开头)。

一旦您将迭代器放入“for”循环,它将不再可用。我的猜测是语言设计者决定在编译器中结合额外的特殊情况(其中已经有两个用于 Iterable 和数组)将其转换为字节码(你不能重用与 iterable 相同的转换)就足够了批评者不实施它。

当您通过迭代器接口在代码中自己执行此操作时,至少会很明显发生了什么。

随着 lambdas 的到来,他们可以使这变得又好又容易:

Iterator<String> iterator = ...;
Collections.each ( iterator, (String s) => { System.out.println(s); } );

List<String> list = ...;
Collections.each ( list, (String s) => { System.out.println(s); } );

在不破坏向后兼容性的情况下,仍然具有相对简单的语法。我怀疑他们会在不同的接口中构建像“each”、“collect”和“map”这样的方法,因为这会破坏向后兼容性,而且你还有数组需要处理。

于 2012-06-26T23:26:33.933 回答
1

我认为答案的一部分可能隐藏在 for-each 循环是语法糖这一事实中。关键是你想做一些人们经常做的事情,更容易。而且(至少以我的经验)成语

Iterator iterator = iterable.iterator();
while( iterator.hasNext() ) {
  Element e = (Element)iterator.next() ;
}

一直在旧式代码中发生。而用多个迭代器做一些花哨的事情却没有。

于 2012-06-26T23:43:55.697 回答