【问题标题】:Why does Java not allow foreach on iterators (only on iterables)? [duplicate]为什么 Java 不允许在迭代器上使用 foreach(仅在可迭代对象上)? [复制]
【发布时间】:2012-06-28 08:11:17
【问题描述】:

可能重复:
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
}

但是,如果我一开始没有可迭代对象,而只有一个迭代器(比如说,因为一个类提供了两个不同的迭代器),我就不能使用语法糖 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.

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

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

【问题讨论】:

  • 或this one。
  • 两者都很接近,但并不完全相同。迭代器不应该是可迭代的,因为您只能迭代一次(没有“重新启动”)。而且我知道你不能在迭代器上使用 foreach(除非你使用我概述的适配器),但我试图理解为什么 Java 是这样设计的。
  • 咩;我倾向于不同意。 1.5 文档中给出的答案只是表明设计人员选择这种方式来处理最常见的用例。有时答案是“只是因为”。
  • 有时设计师有理由以一种或另一种方式设计语言......试图理解他们的推理有什么问题(即使我无法改变事情)?
  • 谁说试图理解他们的推理有什么问题?我说是他们决定去抓低垂的果实,他们就是这样做的,不一定有太多要理解的。

标签: java syntax iterator language-design iterable


【解决方案1】:

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

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

如果语法是按照我的建议围绕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 和Iterator(for 循环应该获得一个新的迭代器或迭代当前的迭代器 - 很容易发生在树状结构!?!)。不幸的是,一个简单的 tie-braker ala “Iterable beats Iterator”是行不通的:它突然引入了运行时与编译时间的差异和泛型问题。

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

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

有一个变体 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) { }

只对数据进行一次迭代。

【讨论】:

  • "使用 Iterator 语法,需要编写..." 为什么?
  • 因为否则我可以让一个类同时实现 Iterable 和 Iterator 并造成很大的混乱。
  • 这与数组有什么关系?无论如何我都可以做到这一点,而无需参考数组——这正是我在回答中提到的那种复杂性(斜线“猜测”:-))。
  • 您希望数组的语法与集合的语法相同,集合是Iterable,但不是Iterators。因此,虽然我们也可以在迭代器上设置一个 for-each 循环,但对于数组和类似数组的结构(例如集合)肯定需要它。
  • @Anony-Mousse:嗯?现在您的论点是您希望 for-each 仅在迭代器而不是可迭代对象上工作吗?你把我弄丢了。
【解决方案2】:

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

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

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

…JSR 说:

这些新接口用于防止语言对java.util 的依赖性,否则会导致这种依赖性。

【讨论】:

  • 感谢您的指点。我现在对可能是什么原因有了一个大概的了解。我将不得不在这方面多考虑一下,然后我会更新。
  • 显然,引入java.lang.Iterable 而不是仅仅使用java.util.Iterator 的基本原理是避免核心语言对java.util 的依赖。 ;)
【解决方案3】:

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

【讨论】:

  • 好吧,如果通过在Iterables 上调用.iterator() 明确地完成“重置”,那么重置也应该是显而易见的。好吧,人们可能仍然可以忽略不重置...
  • Iterable 为您提供了一种可预测的循环机制,因为循环多次迭代每次都会产生相同的结果(假设您没有弄乱数据本身)。如果您可以循环遍历迭代器,则不能这么说(因为正如我所说,它不能被重置)。
  • 对于像队列这样的一些数据类型,消费“for each”的概念是有意义的。允许类型为“for-each-able”而不保证repeatedly可枚举,这似乎是一种有用的能力,并且迭代器天生就能够被读取一次。
  • 该观点与try-with-resources和Stream.forEach()不一致
【解决方案4】:

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

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

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

【讨论】:

  • 从技术上讲,我认为字节码看起来更像一个 for 循环:for (Iterator iterator = iterable.iterator(); iterator.hasNext();)
  • 事实上,在他们提出 for each 语法的 JSR 中,他们使用了这种 for 循环。原因是变量作用域,然后迭代器仅在循环内定义。 while 语法是经典的 Java 教科书,但您还需要在周围添加一个额外的 {} 才能使变量作用域正确。
猜你喜欢
  • 1970-01-01
  • 2021-03-30
  • 1970-01-01
  • 2020-05-10
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多