为什么Java的Iterator不可迭代?


178

为什么Iterator接口不扩展Iterable

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

是故意的还是对Java设计人员的监督?

能够将for-each循环与这样的迭代器一起使用会很方便:

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

其中listSomeObjects()返回迭代器。


1
好的-我看到了你的观点。它仍然是方便的,即使它打破了一个语义一点:]感谢ü所有的答案:]
卢卡斯Bownik

我知道这个问题是很久以前问过的,但是-您是说任何迭代器,还是与某个Collection相关的迭代器?
einpoklum 2015年

Answers:


67

因为迭代器通常指向集合中的单个实例。Iterable意味着可以从一个对象获得迭代器以遍历其元素-无需迭代单个实例(迭代器表示的是)。


25
+1:集合是可迭代的。迭代器不可迭代,因为它不是集合。
S.Lott

50
虽然我同意答案,但不知道我是否同意这种想法。Iterable接口提供了一个方法:Iterator <?> iterator(); 无论如何,我应该能够为每个指定迭代器。我不买
克里斯K

25
@ S.Lott那里很好的循环推理。
yihtserns 2011-02-17

16
@ S.Lott上一次尝试:Collection∈可迭代。Iterator≠Collection∴Iterator∉Iterable
yihtserns 2011年

27
@ S.Lott:收集与该讨论完全无关。收集只是Iterable的许多可能实现之一。事物不是集合这一事实与它是否是可迭代无关。
ColinD

218

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

例如,我通常可以这样写:

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

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


17
@Chris:如果一个实现两次返回同一个迭代器,那么它究竟如何履行Iterator的合同?如果调用iterator并使用结果,则必须在集合上进行迭代-如果相同的对象已经在集合上进行迭代,则不会这样做。您能否给出相同的迭代器两次返回的任何正确实现(对于空集合除外)?
乔恩·斯基特

7
乔恩(Jon),这是一个很好的回应,您的问题确实很棘手。不好意思,这不是公认的答案!Iterable的约定是严格定义的,但是以上内容很好地解释了为什么允许Iterator实现Iterable(for foreach)会破坏接口的精神的原因。
joelittlejohn

3
@JonSkeet,尽管Iterator <T>是有状态的,但Iterable <T>的约定没有说明能够使用两次来获得独立的迭代器,即使在99%的情况下也是如此。Iterable <T>的全部意思是,它允许对象成为foreach的目标。对于那些对Iterator <T>不能成为Iterable <T>感到不满意的人,您可以自由制作这样的Iterator <T>。它一点也不会破坏合同。但是-迭代器本身不应该是可迭代的,因为这会使它循环依赖,并为棘手的设计奠定基础。
Centril 2014年

2
@Centril:对。编辑以指示通常调用iterable两次将为您提供独立的迭代器。
乔恩·斯基特

2
这确实引起了人们的注意。几乎可以通过重置自身来实现实现.iterator()的Iterable Iterator,但是这种设计在某些情况下仍然会中断,例如,如果将其传递给采用Iterable并遍历所有可能对的方法通过嵌套for-each循环来设置元素。
Theodore Murdock 2014年

59

对于我的$ 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循环易于用于枚举,正如其他地方所指出的,它类似于Iterators not Iterables。

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

干杯,


6
我同意。从理论上讲,当获得一个迭代器,使用它的一部分,然后将其放入foreach中(打破foreach的“每个”合约)时,可能会感到困惑,但是我认为没有这个功能是没有足够的理由。
Bart van Heukelom

我们已经更新/增强了循环以接受数组,而不是使数组成为可迭代的集合。您可以合理化这个决定吗?
2013年

我赞成这个答复,这是一个错误。迭代器是有状态的,如果您使用for(item : iter) {...}语法在迭代器上促进迭代,则当同一迭代器被迭代两次时,它将引发错误。想象一下,Iterator被传递到iterateOver方法,而不是Iterable这个例子
Ilya Silvestrov 2014年

2
究竟使用for (String x : strings) {...}or还是while (strings.hasNext()) {...}style 都没有关系:如果第二次尝试遍历迭代器两次将不会产生任何结果,因此我不认为它本身就是反对允许使用增强语法的论点。乔恩的答案是不同的,因为他展示了如何包装一个IteratorIterable会造成问题,因为你会希望能够重复使用多次,你喜欢的话。
巴尼2014年

17

正如其他人指出的,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适应的Iterator一种Iterable变简单了:

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

您在函数中声明了一个类;我想念什么吗?我认为这是非法的。
activedecay

可以在Java的块中定义类。这被称为本地课程
Colin D Bennett

3
感谢for (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

正如其他人所说的,一个Iterable可以被多次调用,每次调用都返回一个新的Iterator。迭代器仅使用一次。因此它们是相关的,但有不同的用途。但是,令人沮丧的是,“ compact for”方法仅适用于可迭代的方法。

我将在下面描述的是一种兼具两全其美的方法-即使数据的基础序列是一次性的,也要返回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的示例中,这意味着对数据库事务中的结果进行迭代。

因此有很多警告,但是在许多情况下这仍然是一个有用的习惯用法。


很好,但是您returning a fresh Iterator on each call应该伴随着为什么,即防止并发问题……
Anirudha

7

令人难以置信的是,没有人给出这个答案。这是Iterator使用新的Java 8 Iterator.forEachRemaining()方法“轻松”遍历的方法:

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

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

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

5

Iterator是允许您迭代某些内容的界面。它是一种遍历某种集合的实现。

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

在Java8中,这使生活变得很轻松...如果您有一个Iterator但需要一个Iterable,则可以简单地执行以下操作:

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

这也适用于for循环:

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

2

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

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

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

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


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语法(通过Consumer)。

这是一个例子。的List接口扩展Iterable接口,如任何列表中携带一个forEach方法。

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

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


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

0

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

for(Object o : someContainer) {
}

与一些container实例 SomeContainer extends Iterable<Object>




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);

-1

您可以尝试以下示例:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.