web-dev-qa-db-ja.com

JavaのIteratorがIterableではないのはなぜですか?

なぜIteratorインターフェイスはIterableを拡張しないのですか?

iterator()メソッドは、単にthisを返すことができます。

それは意図的なものですか、それともJavaのデザイナーの監視ですか?

次のような反復子でfor-eachループを使用できると便利です。

_for(Object o : someContainer.listSomeObjects()) {
    ....
}
_

ここで、listSomeObjects()は反復子を返します。

168
Łukasz Bownik

イテレータは通常、コレクション内の単一のインスタンスを指すためです。 Iterableは、オブジェクトからイテレータを取得してその要素をトラバースできることを意味します。イテレータが表すものである単一のインスタンスを反復する必要はありません。

63
PaulJWilliams

イテレータはステートフルです。 Iterable.iterator()を2回呼び出すと、independent iteratorsが得られます-ほとんどの反復可能オブジェクトについてはとにかくそれは明らかにあなたのシナリオには当てはまりません。

たとえば、私は通常次のように書くことができます:

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

これにより、コレクションが2回出力されますが、スキームを使用すると、2番目のループは常に即座に終了します。

211
Jon Skeet

私の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ループの外側で発生します:ループ内では、イテレータによって返されたアイテムを一度に1つずつ順方向にしか進行しません。

また、これを許可すると、列挙のforループを簡単に使用できるようになります。これは、他で指摘されているように、イテラブルではなくイテレータに類似しています。

そのため、IteratorにIterableを実装させないでください。ただし、forループを更新してどちらかを受け入れます。

乾杯、

57
Barney

他の人が指摘したように、 IteratorIterable は2つの異なるものです。

また、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 IteratorIterableに適合させることはより簡単になります:

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

他の人が言ったように、Iterableは複数回呼び出すことができ、各呼び出しで新しいIteratorを返します。イテレータは一度だけ使用されます。したがって、それらは関連していますが、異なる目的に役立ちます。しかし、不満なことに、「コンパクト」メソッドは反復可能オブジェクトでのみ機能します。

以下で説明するのは、両方の長所を活用する1つの方法です。基礎となるデータシーケンスが1回限りの場合でも(より良い構文のために)Iterableを返します。

秘Theは、実際に作業をトリガーするIterableの匿名実装を返すことです。そのため、1回限りのシーケンスを生成する作業を行ってからその反復子を返す代わりに、アクセスされるたびに作業をやり直すIterableを返します。それは無駄に思えるかもしれませんが、多くの場合、とにかく一度だけIterableを呼び出すだけで、それを複数回呼び出しても、それはまだ合理的なセマンティクスを持っています(IteratorをIterableのように見える単純なラッパーとは異なり、これは2回使用すると失敗します)。

たとえば、データベースから一連のオブジェクトを提供する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ループを使用できます。

このアプローチは「怠laz」です-Iterableが要求されたときに作業は行われませんが、コンテンツが繰り返されるときにのみ行われます-そして、その結果に注意する必要があります。 DAOを使用した例では、データベーストランザクション内の結果を反復処理します。

そのため、さまざまな注意事項がありますが、多くの場合、これは依然として有用なイディオムになり得ます。

8
andrew cooke

信じられないほど、この答えを誰もまだ出していない。以下に、新しいJava 8 Iterator.forEachRemaining() メソッドを使用してIteratorを反復処理する方法を示します。

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

もちろん、foreachループで直接動作する「単純な」ソリューションがあり、IteratorラムダでIterableをラップします。

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

私もこれをしている多くの人を見ています:

_public Iterator iterator() {
    return this;
}
_

しかし、それは正しくありません!この方法はあなたが望むものではありません!

メソッドiterator()は、最初から新しいイテレータを返すことになっています。そのため、次のようなことをする必要があります。

_public class IterableIterator implements Iterator, Iterable {

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

  public Iterator iterator() {
    return new MyClass(this.somedata);
  }

  // methods of Iterator

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

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

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

問題は、これを実行する抽象クラスを作成する方法はありますか? IterableIteratorを取得するには、next()とhasNext()の2つのメソッドを実装するだけです

2

回避策を求めてここに来た場合は、 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
1
serv-inc

私は受け入れられた答えに同意しますが、自分の説明を追加したいと思います。

  • イテレータはトラバースの状態を表します。たとえば、現在の要素をイテレータから取得して、次の要素に進むことができます。

  • Iterableはトラバース可能なコレクションを表し、必要な数のイテレータを返すことができます。各イテレータはトラバースの独自の状態を表します。1つのイテレータは最初の要素を指し、別のイテレータは3番目の要素を指します。

Java forループがIteratorとIterableの両方を受け入れる場合、それは素晴らしいでしょう。

1
Dagang

イテレータはステートフルであり、「次の」要素を持ち、繰り返し処理されると「使い果たされます」。問題の場所を確認するには、次のコードを実行します。何個の数字が印刷されますか?

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

関連する注意事項として、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

0
Fabrizio

簡単にするために、IteratorとIterableは2つの異なる概念であり、Iterableは単に「Iteratorを返すことができる」の省略形です。あなたのコードは次のようにすべきだと思います:

for(Object o : someContainer) {
}

someContainer instanceof SomeContainer extends Iterable<Object>

0
dfa

余談ですが、ScalaにはイテレータにtoIterable()メソッドがあります。 イテレータからイテラブルへの暗黙的または明示的なスカラ変換 を参照してください。

0
Catweazle