为什么 Java 的迭代器不是可迭代的?

为什么 Iterator接口不扩展 Iterable

iterator()方法可以简单地返回 this

它是故意的还是只是 Java 设计者的疏忽?

如果能够使用 for-each 循环和这样的迭代器,那将是非常方便的:

for(Object o : someContainer.listSomeObjects()) {
....
}

其中 listSomeObjects()返回迭代器。

51816 次浏览

因为迭代器通常指向集合中的单个实例。Iterable 意味着可以从一个对象获得一个迭代器来遍历它的元素——并且不需要遍历单个实例,这就是迭代器所代表的。

迭代器是有状态的。这个想法是,如果您调用 Iterable.iterator()两次,您将得到 独立迭代器-对于大多数迭代器,无论如何。你的情况显然不是这样。

例如,我通常可以写:

public void iterateOver(Iterable<String> strings)
{
for (String x : strings)
{
System.out.println(x);
}
for (String x : strings)
{
System.out.println(x);
}
}

这应该打印两次集合-但与您的方案第二个循环将总是立即终止。

为了简单起见,Iterator 和 Iterable 是两个截然不同的概念,Iterable 只是“我可以返回一个迭代器”的简写。我认为你的代码应该是:

for(Object o : someContainer) {
}

带有一些 SomeContainer extends Iterable<Object>的容器实例

我还看到许多人这样做:

public Iterator iterator() {
return this;
}

但这不代表这是对的! 这种方法不会是你想要的!

方法 iterator()应该返回一个从头开始的新迭代器。 所以我们需要这样做:

public class IterableIterator implements Iterator, Iterable {


//Constructor
IterableIterator(SomeType initdata)
{
this.initdata = iter.initdata;
}
// methods of Iterable


public Iterator iterator() {
return new IterableIterator(this.intidata);
}


// methods of Iterator


public boolean hasNext() {
// ...
}


public Object next() {
// ...
}


public void remove() {
// ...
}
}

问题是: 有没有办法让一个抽象类来执行这个操作? 因此,要获得 IterableIterator,只需要实现两个方法 next ()和 hasNext ()

顺便说一句: Scala 在 Iterator 有一个 toIterable ()方法

对于我的 $0.02,我完全同意迭代器不应该实现 Iterable,但是我认为增强的 For 循环应该接受任何一个。我认为整个“让迭代器可迭代”的争论是围绕语言中的缺陷展开的。

引入增强的 for 循环的全部原因是,它“消除了在集合和数组上迭代时迭代器和索引变量的枯燥和易错性”[ 1]。

Collection<Item> items...


for (Iterator<Item> iter = items.iterator(); iter.hasNext(); ) {
Item item = iter.next();
...
}


for (Item item : items) {
...
}

那么为什么对迭代器来说同样的论点不成立呢?

Iterator<Iter> iter...
..
while (iter.hasNext()) {
Item item = iter.next();
...
}


for (Item item : iter) {
...
}

在这两种情况下,都删除了对 hasNext ()和 next ()的调用,并且在内部循环中没有对迭代器的引用。是的,我知道可以重用 Iterables 来创建多个迭代器,但是所有这些都发生在 for 循环之外: 在循环内部,在迭代器返回的项上,每次只有一个前进的项。

此外,允许这样做还可以使枚举的 for 循环更容易使用,正如在其他地方已经指出的那样,它类似于迭代器而不是 Iterables。

因此,不要让 Iterator 实现 Iterable,而是更新 for 循环来接受其中任何一个。

干杯,

正如其他人指出的,IteratorIterable是两个不同的东西。

而且,Iterator实现早于增强的循环。

用一个简单的适配器方法来克服这个限制也是很容易的,当与静态方法导入一起使用时,适配器方法如下所示:

for (String line : in(lines)) {
System.out.println(line);
}

实施例子:

  /**
* Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for
* loops. If {@link Iterable#iterator()} is invoked more than once, an
* {@link IllegalStateException} is thrown.
*/
public static <T> Iterable<T> in(final Iterator<T> iterator) {
assert iterator != null;
class SingleUseIterable implements Iterable<T> {
private boolean used = false;


@Override
public Iterator<T> iterator() {
if (used) {
throw new IllegalStateException("SingleUseIterable already invoked");
}
used = true;
return iterator;
}
}
return new SingleUseIterable();
}

在 Java 8中,将一个 Iterator改编成一个 Iterable变得更加简单:

for (String s : (Iterable<String>) () -> iterator) {

正如其他人所说,可以多次调用 Iterable,在每次调用时返回一个新的迭代器; 迭代器只使用一次。因此,它们是相关的,但服务于不同的目的。然而,令人沮丧的是,“ compactfor”方法只适用于可迭代的。

我将在下面描述的是一种兼顾两者的方法——即使数据的底层序列是一次性的,也返回 Iterable (用于更好的语法)。

诀窍是返回实际触发工作的 Iterable 的匿名实现。因此,您不需要执行生成一次性序列的工作,然后在该序列上返回一个 Iterator,而是返回一个 Iterable,每次访问该 Iterable 时,它都会重新执行该工作。这可能看起来很浪费,但通常你只会调用 Iterable 一次,即使你多次调用它,它仍然有合理的语义(不像一个简单的包装器使 Iterator“看起来像”一个 Iterable,这不会失败,如果使用两次)。

例如,假设我有一个 DAO,它从数据库中提供了一系列对象,我想通过迭代器提供对这些对象的访问(例如,如果不需要,就避免在内存中创建所有对象)。现在我可以只返回一个迭代器,但这使得在循环中使用返回值变得很难看。因此,我用匿名 Iterable 来包装所有内容:

class MetricDao {
...
/**
* @return All known metrics.
*/
public final Iterable<Metric> loadAll() {
return new Iterable<Metric>() {
@Override
public Iterator<Metric> iterator() {
return sessionFactory.getCurrentSession()
.createQuery("from Metric as metric")
.iterate();
}
};
}
}

这可以在代码中使用,如下所示:

class DaoUser {
private MetricDao dao;
for (Metric existing : dao.loadAll()) {
// do stuff here...
}
}

它允许我在保持增量内存使用的同时使用紧凑的 for 循环。

这种方法是“惰性”的——当请求 Iterable 时,工作不会完成,但只有在迭代内容时才会完成——您需要了解这样做的后果。在具有 DAO 的示例中,这意味着在数据库事务中迭代结果。

因此,有各种各样的警告,但这仍然可以是一个有用的习语在许多情况下。

你可以试试下面的例子:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
System.out.println(iterator.next());
}

另外,您可能会发现 ApacheCommons Collections4中的 IteratorIterable 适配器很有用。只需从迭代器创建一个实例,就可以得到相应的迭代器。

Https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/iterators/iteratoriterable.html

ID: org.apache.commons: commons-Collections4:4.0

Iterator是一个接口,允许您对某些内容进行迭代。它是通过某种集合移动的实现。

Iterable是一个函数式接口,它表示某些内容包含一个可访问的迭代器。

在 Java8中,这让生活变得非常简单... ... 如果你有一个 Iterator但是需要一个 Iterable,你可以简单地做:

Iterator<T> someIterator;
Iterable<T> = ()->someIterator;

这也适用于 for 循环:

for (T item : ()->someIterator){
//doSomething with item
}

如果您来这里是为了寻找解决方案,那么您可以使用 可迭代的(可用于 Java 1.6及以上版本)

示例用法(逆向向量)。

import java.util.Vector;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.collections4.iterators.ReverseListIterator;
public class Test {
public static void main(String ... args) {
Vector<String> vs = new Vector<String>();
vs.add("one");
vs.add("two");
for ( String s: vs ) {
System.out.println(s);
}
Iterable<String> is
= new IteratorIterable(new ReverseListIterator(vs));
for ( String s: is ) {
System.out.println(s);
}
}
}

指纹

one
two
two
one

迭代器是有状态的,它们有一个“ next”元素,并且一旦迭代就会变得“筋疲力尽”。要查看问题出在哪里,请运行以下代码,输出多少个数字?

Iterator<Integer> iterator = Arrays.asList(1,2,3).iterator();
Iterable<Integer> myIterable = ()->iterator;
for(Integer i : myIterable) System.out.print(i);
System.out.println();
for(Integer i : myIterable) System.out.print(i);

令人难以置信的是,迄今为止还没有人给出这个答案。下面是使用新的 Java8Iterator.forEachRemaining()方法在 Iterator上“轻松”迭代的方法:

Iterator<String> it = ...
it.forEachRemaining(System.out::println);

当然,有一个“更简单”的解决方案可以直接使用 foreach 循环,将 Iterator封装在 Iterable lambda 中:

for (String s : (Iterable<String>) () -> it)
System.out.println(s);

我同意接受的答案,但想补充我自己的解释。

  • 迭代器表示遍历的状态,例如,您可以从迭代器中获取当前元素,然后移动到下一个元素。

  • Iterable 表示一个可以被遍历的集合,它可以返回任意多个迭代器,每个迭代器表示它自己的遍历状态,一个迭代器可能指向第一个元素,而另一个可能指向第三个元素。

如果 Java for 循环同时接受 Iterator 和 Iterable 就好了。

避免依赖于 java.util

根据最初的 JSR 为 JavaTM 编程语言增强的 for 循环,建议的接口:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator < br/> (建议在 java.util.Iterator上改装,但显然这从未发生过)

... 被设计成使用 java.lang包的名称空间而不是 java.util

引用 JSR 的话:

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


顺便说一下,旧的 java.util.Iterable在 Java8 + 中获得了一个新的 forEach方法,用于 lambda 语法(传递一个 Consumer)。

下面是一个例子。 List接口扩展了 Iterable接口,因为任何列表都带有 forEach方法。

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );