为什么Java不允许在迭代器上使用foreach(仅在迭代器上)?

时间:2012-06-26 22:45:29

标签: java syntax iterator language-design iterable

  

可能重复:
  Why is Java's Iterator not an Iterable?

     

Idiomatic way to use for-each loop given an iterator?

     

Can we use for-each loop for iterating the objects of Iterator type?

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
}

但是,如果我首先没有迭代,但只有一个迭代器(比如,因为一个类提供了两个不同的迭代器),我不能使用语法sugar 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)”将处理所有元素两次(并忘记获得一个新的迭代器)?或者还有其他原因吗?

或者是否有一些我不知道的语言技巧?

5 个答案:

答案 0 :(得分:34)

  

有没有人知道为什么语言是这样设计的?

因为for-each只对那些的事物有意义,并且对iter ators 没有意义。如果你已经有了一个迭代器,那么你已经拥有了一个简单循环所需要的东西。

比较:我从iter 开始:

// Old way
Iterator<Thingy> it = iterable.iterator();
while (it.hasNext()) {
    Thingy t = it.next();
    // Use `t`
}

// New way
for (Thingy t : iterable) {
    // Use `t`
}

我开始使用迭代器:

// Old/current way
while (iterator.hasNext()) {
    Thing t = iterator.next();
    // Use `t`
}

// Imagined way
for (Thingy t : iterator) {
   // Use `t`
}

在第二个例子中没有太多内容,它通过创建一个特殊情况使for-each的语义变得复杂。

“为什么”问题在没有针对参与决策的主要参与者时总是很难,但我的猜测是增加的复杂性不值得边际效用。


那就是说,我可以看到一个“增强的while循环”结构:

while (Thingy t : iterator) {
   // Use `t`
}

...它取决于迭代器目前的位置......嗯,也许它会让人太困惑。 : - )

答案 1 :(得分:20)

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

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

如果我提出的语法是围绕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) { }

现在,如果有人实现 {/ 1>} IterableIterator,那么我们最终会处于不明确的情况下(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) { }

只迭代数据一次。

答案 2 :(得分:8)

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

答案 3 :(得分:7)

因为“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”之类的方法构建到不同的接口中,因为这样会破坏向后兼容性,而且还有阵列需要处理。

答案 4 :(得分:1)

我认为答案的一部分可能隐藏在for-each循环是语法糖的事实中。关键是你要做一些人们做了很多事情,更容易。并且(至少根据我的经验)成语

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

始终在旧式代码中出现。用多个迭代器做奇特的东西却没有。