184

为什么Iterator接口不扩展Iterable

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

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

能够使用带有迭代器的 for-each 循环会很方便,如下所示:

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

wherelistSomeObjects()返回一个迭代器。

4

16 回答 16

226

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

例如,我通常可以写:

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

那应该将集合打印两次-但是使用您的方案,第二个循环总是会立即终止。

于 2009-05-08T10:23:35.873 回答
68

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

于 2009-05-08T10:23:01.943 回答
65

对于我的 0.02 美元,我完全同意 Iterator 不应该实现 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 循环用于枚举,正如在其他地方指出的那样,它类似于迭代器而不是迭代器。

所以不要让Iterator实现Iterable,而是更新for循环来接受。

干杯,

于 2011-03-10T23:18:48.053 回答
18

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

此外,Iterator实现早于增强的 for 循环。

用一个简单的适配器方法来克服这个限制也是微不足道的,当与静态方法导入一起使用时,它看起来像这样:

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 中,使 an 适应IteratoranIterable变得更简单:

for (String s : (Iterable<String>) () -> iterator) {
于 2011-12-18T21:59:23.607 回答
9

令人难以置信的是,还没有其他人给出这个答案。Iterator以下是使用新的 Java 8方法“轻松”迭代 an的Iterator.forEachRemaining()方法:

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

当然,有一个“更简单”的解决方案可以直接与 foreach 循环一起使用,将 包装IteratorIterablelambda 中:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);
于 2016-12-21T06:35:55.230 回答
8

正如其他人所说,一个 Iterable 可以被多次调用,每次调用都返回一个新的 Iterator;迭代器只使用一次。所以它们是相关的,但服务于不同的目的。然而,令人沮丧的是,“compact for”方法仅适用于可迭代对象。

我将在下面描述的是一种两全其美的方法——即使底层数据序列是一次性的,也返回一个 Iterable(为了更好的语法)。

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

例如,假设我有一个 DAO,它提供了来自数据库的一系列对象,并且我想通过迭代器提供对它的访问(例如,如果不需要,避免在内存中创建所有对象)。现在我可以只返回一个迭代器,但这使得在循环中使用返回的值很难看。因此,我将所有内容都包装在 anon 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 的示例中,这意味着迭代数据库事务中的结果。

所以有各种警告,但在许多情况下这仍然是一个有用的习惯用法。

于 2012-02-06T15:49:26.243 回答
6

Iterator是一个允许你迭代某些东西的接口。它是在某种集合中移动的一种实现。

Iterable是一个功能接口,表示某物包含可访问的迭代器。

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

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

这也适用于 for 循环:

for (T item : ()->someIterator){
    //doSomething with item
}
于 2015-11-04T00:23:54.290 回答
2

我也看到很多人这样做:

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() 这两个方法

于 2010-10-03T10:54:07.973 回答
2

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

  • Iterator 表示遍历的状态,例如,您可以从一个迭代器中获取当前元素并继续前进到下一个。

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

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

于 2017-09-06T00:37:26.570 回答
2

避免依赖java.util

根据原始 JSR,Java™ 编程语言的增强 for 循环,建议的接口:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (建议改装到java.util.Iterator,但显然这从未发生过)

…被设计为使用java.lang包命名空间而不是java.util.

引用 JSR:

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


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

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

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );
于 2019-10-02T22:55:28.070 回答
1

如果您来这里是为了寻找解决方法,您可以使用IteratorIterable。(适用于 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
于 2016-03-07T19:25:35.180 回答
0

为了简单起见,Iterator 和 Iterable 是两个截然不同的概念,Iterable 只是“I can return an Iterator”的简写。我认为你的代码应该是:

for(Object o : someContainer) {
}

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

于 2009-05-08T10:37:54.780 回答
0

顺便说一句:Scala 在 Iterator 中有一个 toIterable() 方法。请参阅从迭代器到可迭代的 scala 隐式或显式转换

于 2010-11-09T10:12:19.190 回答
0

在相关说明中,您可能会发现 Apache Commons 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

于 2015-03-16T16:00:33.537 回答
0

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

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);
于 2016-07-30T12:27:15.050 回答
-1

您可以尝试以下示例:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
于 2013-06-19T10:10:59.250 回答