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


178

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

iterator()この方法は、単純に返すことができますthis

それは意図的なものですか、それともJavaのデザイナーの見落としでしょうか?

次のようなイテレータでfor-eachループを使用できると便利です。

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

where listSomeObjects()はイテレータを返します。


1
わかりました。あなたの意見は大違いです。]:]すべての答えのためにUをありがとう:それは意味論少し壊れた場合でも、それはまだ便利である
ルカシュBownik

この質問はずっと前に尋ねられたことに気づきましたが、イテレータを意味しているのでしょうか、それともコレクションに関連するイテレータを意味しているのでしょうか。
einpoklum 2015年

回答:


67

イテレータは通常、コレクション内の単一のインスタンスを指すためです。反復可能とは、オブジェクトから反復子を取得してその要素を走査できることを意味します。反復子が表すのは、単一のインスタンスを反復する必要がないことです。


25
+1:コレクションは反復可能です。イテレータはコレクションではないため、反復可能ではありません。
S.Lott、2009年

50
私は答えに同意しますが、私がメンタリティに同意するかどうかはわかりません。Iterableインターフェースは単一のメソッドを提供します:Iterator <?> iterator(); どのような場合でも、for-eachにイテレーターを指定できるはずです。買わない。
Chris K、

25
@ S.Lottいい循環推論があります。
yihtserns、2011

16
@ S.Lott最後の試み:コレクション∈反復可能。イテレーター≠コレクション∴イテレーター∉イテラブル
yihtserns

27
@ S.Lott:コレクションは、このディスカッションとはまったく関係ありません。コレクションは、Iterableの多くの可能な実装の1つにすぎません。何かがコレクションではないという事実は、それがIterableであるかどうかには関係ありません。
ColinD

218

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

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

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

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


17
@Chris:実装が同じイテレータを2回返す場合、いったいどのようにしてイテレータの契約を満たすことができるでしょうか?iterator結果を呼び出して使用する場合は、コレクションを反復処理する必要があります。同じオブジェクトが既にコレクションを反復処理している場合は、反復処理を実行しません。あなたは与えることができます任意の同じイテレータが二度返されます(空のコレクション以外の)正しい実装を?
Jon Skeet、

7
これは素晴らしい応答です。ジョン、あなたは本当に問題の核心をここに持っています。残念ながら、これは受け入れられない答えです。Iterableのコントラクトは厳密に定義されていますが、上記は、IteratorにIterable(forforeach)の実装を許可するとインターフェイスの精神が壊れる理由を説明しています。
joelittlejohn

3
@JonSkeet Iterator <T>がステートフルである場合、Iterable <T>のコントラクトは、独立したイテレータを取得するために2回使用できることについては何も述べていません。Iterable <T>は、オブジェクトがforeachのターゲットになることを許可していると言っています。Iterator <T>がIterable <T>でないことに不満がある場合は、そのようなIterator <T>を自由に作成できます。それは契約を少しも壊しません。ただし、Iterator自体はIterableであってはなりません。循環的に依存し、不快なデザインの基礎となるからです。
Centril

2
@Centril:そうです。通常iterable2回呼び出すと独立したイテレータが得られることを示すように編集しました。
Jon Skeet、2014年

2
これはその核心に達します。それ自体をリセットすることで.iterator()を実装するIterable Iteratorを実装することはほぼ可能ですが、この設計は、たとえばIterableを取り、すべての可能なペアをループするメソッドに渡された場合など、状況によっては壊れます。 for-eachループをネストすることによる要素の。
セオドアマードック14

60

私の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()の呼び出しは削除されており、内部ループの反復子への参照はありません。はい、Iterableを再利用して複数のイテレーターを作成できることは理解していますが、すべてがforループの外側で発生します。ループ内では、イテレーターによって返されたアイテムに対して、一度に1つのアイテムだけが順方向に進行します。

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

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

乾杯、


6
同意する。理論的には、イテレータを取得し、その一部を使用してから、それをforeachに入れる(foreachの「each」コントラクトを破る)ときに混乱が生じる可能性がありますが、これがこの機能がない理由として十分ではないと思います。
Bart van Heukelom、2011年

ループを更新/強化して、配列を反復可能なコレクションにする代わりに、配列を受け入れるようにしました。この決定を合理化できますか?
Val

私はこの返事に賛成しましたが、それは間違いでした。イテレータはステートフルです。for(item : iter) {...}構文を使用してイテレータを反復処理すると、同じイテレータが2回反復されるとエラーが発生します。この例ではなく、メソッドにIterator渡されると想像してください。iterateOverIterable
Ilya Silvestrov 2014

2
for (String x : strings) {...}またはwhile (strings.hasNext()) {...}スタイルを使用するかどうかは問題ではありません。イテレータを2回ループして2回ループしても結果が得られないため、それ自体は拡張構文を許可することに対する引数とは見なされません。Jonの答えは異なります。なぜなら、彼は、IteratorinをラップするIterableことで問題が発生する方法を示しているからです。
Barney

17

他の人が指摘し、IteratorそしてIterable二つの異なるものです。

また、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のブロックで定義できます。ローカルクラス
コリンDベネット

3
ありがとうございましたfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka 2018

8

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

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

トリックは、実際に作業をトリガーするIterableの匿名実装を返すことです。したがって、1回限りのシーケンスを生成する作業を行ってからIteratorを返す代わりに、アクセスされるたびに作業をやり直すIterableを返します。それは無駄に思えるかもしれませんが、とにかく一度Iterableを呼び出すのは1回だけであり、複数回呼び出す場合でも、合理的なセマンティクスを持っています(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ループを使用できます。

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

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


いいansですが、並行性の問題を防ぐためにreturning a fresh Iterator on each callなぜ一緒に行う必要があります
Anirudha

7

信じられないことに、まだ誰もこの答えを出していません。Iterator新しいJava 8 Iterator.forEachRemaining()メソッドを使用して、「簡単に」反復する方法を次に示します。

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

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

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は、トラバースできるコレクションを表します。必要な数のイテレータを返すことができ、それぞれが独自のトラバース状態を表します。1つのイテレータが最初の要素を指し、別のイテレータが3番目の要素を指している場合があります。

Java forループが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+でラムダ構文(aを渡す)で使用するためのjava.util.Iterable新しいforEachメソッドを取得しました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を取得するには、2つのメソッド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は2つの異なる概念です。Iterableは単に「Iteratorを返すことができる」の省略形です。私はあなたのコードは次のようになるはずだと思います:

for(Object o : someContainer) {
}

someContainer instanceof 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());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.