83

重複の可能性:
Java の Iterator が Iterable でないのはなぜですか?

イテレータを指定して for-each ループを使用する慣用的な方法は?

Iterator 型のオブジェクトを反復処理するために for-each ループを使用できますか?

foreach ループは、私が知る限り、Java 5 で追加された構文シュガーです。

Iterable<O> iterable;
for(O o : iterable) {
    // Do something
}

本質的に同じバイトコードを生成します

Iterable<O> iterable;
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) {
    O o = iter.next();
    // Do something
}

しかし、そもそも iterable がなく、イテレーターしかない場合 (たとえば、クラスが 2 つの異なるイテレーターを提供するため)、構文シュガー foreach ループを使用することはできません。明らかに、私はまだ昔ながらのスタイルの反復を行うことができます. しかし、私は実際にやりたい:

Iterator<O> iter;
for(O o : iter /* Iterator<O>, not Iterable<O>! */) {
     // Do something
}

そしてもちろん、私は偽物を作ることができますIterable:

class Adapter<O> implements Iterable<O> {
    Iterator<O> iter;

    public Adapter(Iterator<O> iter) {
        this.iter = iter;
    }

    @Override
    public Iterator<O> iterator() {
        return iter;
    }
}

(実際、これは Iterable API の醜い悪用です。反復できるのは 1 回だけです!)

反復可能ではなく設計されている場合Iterator、多くの興味深いことができます。

for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections

for(O o : list.backwardsIterator()) {} // Or backwards

Iterator<O> iter;
for(O o : iter) {
    if (o.something()) { iter.remove(); }
    if (o.something()) { break; }
}
for(O : iter) { } // Do something with the remaining elements only.

言語がこのように設計された理由を誰か知っていますか? Iteratorクラスが と の両方を実装する場合のあいまいさを避けるにはIterable? 「for(O o : iter)」がすべての要素を 2 回処理する (そして新しい反復子を取得するのを忘れる) と想定するプログラマーのエラーを回避するには? それとも、これには別の理由がありますか?

または、私が知らない言語のトリックはありますか?

4

4 に答える 4

24

だから私は今、やや合理的な説明をしています:

短いバージョン:構文はイテレータを持たないarraysにも適用されるためです。

Iterator私が提案したように構文を設計すると、配列と矛盾することになります。3つのバリエーションを挙げましょう:

A) Java 開発者が選択したもの:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }

は同じように動作し、配列とコレクション全体で高度に一貫しています。ただし、反復子は従来の反復スタイルを使用する必要があります (少なくともエラーが発生する可能性はありません)。

B)配列とIterators:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

現在、配列とコレクションには一貫性がありません。ただし、配列と ArrayList は非常に密接に関連しており、同じように動作する必要があります。任意の時点で、たとえば配列を実装するように言語が拡張さIterableれると、一貫性がなくなります。

C) 3 つすべてを許可する:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }

ここで、誰かがandの両方 を実装しているという不明確な状況に陥った場合(for ループは新しいイテレータを取得するか、現在のイテレータを反復する必要がありますか? ツリーのような構造では簡単に起こります!?!)。残念ながら、単純なタイブレーカー ala "Iterable Beats Iterator" は機能しません。実行時間とコンパイル時間の差とジェネリックの問題が突然発生します。IterableIterator

ここで突然、コレクション/イテラブルまたは配列のどちらを反復処理するかについて注意を払う必要があります。この時点では、大きな混乱を招く代わりにほとんどメリットが得られませんでした。

Java (A) における「for each」の方法は非常に一貫しており、プログラミング エラーがほとんど発生せず、配列を通常のオブジェクトに変換するという将来の変更が可能になります。

おそらく問題なく動作するバリアントD)があります: イテレータのみの for-each です。.iterator()できれば、プリミティブ配列にメソッドを追加することによって:

Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

しかし、これにはコンパイラだけでなくランタイム環境への変更が必要であり、下位互換性が失われます。さらに、言及された混乱はまだ存在しています

Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }

データを 1 回だけ反復します。

于 2012-06-26T23:23:27.507 に答える
13

Iterableインターフェースは、元のJSRで説明されているように、まさにその目的のために作成されました(forループが強化されています)が、Iteratorインターフェースはすでに使用されていました。

JSRで説明されている新しいインターフェイスについて(パッケージ名に注意してください):

  • java.lang.Iterable
  • java.lang.ReadOnlyIteratorjava.util.Iterator(実際には行われていませんが、JSRで後付けすることが提案されています)

…JSRは言う:

これらの新しいインターフェースは、言語の依存を防ぐのに役立ちますjava.util

于 2012-06-26T22:52:20.407 に答える
9

「for」ループはイテレータにとって破壊的であるためです。ListIterator サブインターフェースを実装しない限り、Iterator をリセットする (つまり、最初に戻す) ことはできません。

イテレータを「for」ループに通すと、使用できなくなります。私の推測では、言語設計者は、これをコンパイラの追加の特殊なケース (Iterable と配列には既に 2 つある) と組み合わせて、これをバイトコードに変換する (同じ変換を iterable として再利用することはできません) で十分であると判断しました。それを実装しないことへの批判者。

イテレータ インターフェイスを介してコード内でこれを自分で行うと、少なくとも何が起こっているのかが明らかになります。

ラムダが来ると、彼らはこれを素晴らしく簡単にすることができます:

Iterator<String> iterator = ...;
Collections.each ( iterator, (String s) => { System.out.println(s); } );

List<String> list = ...;
Collections.each ( list, (String s) => { System.out.println(s); } );

下位互換性を損なうことなく、比較的単純な構文を維持しています。「each」、「collect」、「map」などのメソッドをさまざまなインターフェースに組み込むとは思えません。これは、下位互換性が失われ、さらに配列を処理する必要があるためです。

于 2012-06-26T23:26:33.933 に答える
1

for-each ループがシンタックス シュガーであるという事実に、答えの一部が隠されているのではないかと思います。要点は、人々がよく行うことをもっと簡単にしたいということです。そして(少なくとも私の経験では)イディオム

Iterator iterator = iterable.iterator();
while( iterator.hasNext() ) {
  Element e = (Element)iterator.next() ;
}

古いスタイルのコードでは常に発生します。そして、複数のイテレータで凝ったことをするのはそうではありませんでした。

于 2012-06-26T23:43:55.697 に答える