web-dev-qa-db-ja.com

なぜJavaイテレータでforeachを許可しない(イテレート可能オブジェクトでのみ)?

可能性のある複製:
なぜJavaのIteratorがIterableではないのか

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

イテレータ型のオブジェクトを反復処理するために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
}

ただし、そもそもイテレータを使用せず、イテレータのみを使用する場合(クラスが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のい乱用は、一度しか繰り返せないためです!)

反復可能ではなく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.

why言語がこのように設計されたことを誰もが知っていますか?クラスがIteratorIterableの両方を実装する場合のあいまいさを回避するには? 「for(O o:iter)」がすべての要素を2回処理することを前提とするプログラマーのエラーを回避するため(そして、新しいイテレーターを取得することを忘れる)。または、これには他の理由がありますか?

それとも、私が知らない言語トリックがありますか?

67
Anony-Mousse

だから私は今やや合理的な説明があります:

ショートバージョン:構文は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は非常に密接に関連しており、同じように動作する必要があります。これで、任意の時点で、言語はextendedになり、たとえば配列はIterableを実装し、一貫性がなくなります。

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

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

誰かがbothIterableおよびIterator(forループが想定されている場合)新しいイテレータを取得するか、現在のイテレータを反復する-ツリーのような構造で簡単に起こります!?!)。単純なタイブレーカーala "Iterable beats Iterator"は残念ながら実行できません。突然、ランタイムとコンパイル時間の差およびジェネリックの問題が発生します。

突然、コレクション/反復可能要素または配列を反復処理するかどうかに注意を払う必要があります。この時点で、大きな混乱を招きつつもほとんど利益を得ていません。

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

おそらく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回だけ繰り返します。

20
Anony-Mousse

誰もが知っているなぜ言語はこのように設計されましたか?

For-eachはiter ableの場合にのみ意味を持ち、iter atorsの場合には意味をなさないためです。イテレータが既にある場合は、単純なループでこれを行うために必要なものがすでにあります。

比較:私はイテレーターから始めますable

// Old way
Iterator<Thingy> it = iterable.iterator();
while (it.hasNext()) {
    Thingy t = it.next();
    // Use `t`
}

// New way
for (Thingy t : iterable) {
    // Use `t`
}

私はイテレータで始めます。

// Old/current way
while (iterator.hasNext()) {
    Thing t = iterator.next();
    // Use `t`
}

// Imagined way
for (Thingy t : iterator) {
   // Use `t`
}

2番目の例ではあまり説明していませんが、特別なケースを作成することでfor-eachのセマンティクスを複雑にしています。

決定に関与する主要な参加者に向けられていない場合、「なぜ」の質問は常に難しいですが、私の推測では、追加された複雑さは限界ユーティリティの価値がなかったと思います。


とはいえ、「強化されたwhileループ」構造を見ることができました。

while (Thingy t : iterator) {
   // Use `t`
}

...これは、イテレータが現在どこにあるのかを見つけます... :-)

35
T.J. Crowder

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

8
assylias

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

イテレータを「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」などのメソッドを異なるインターフェースに構築するとは思えませんが、これは後方互換性を損なうだけでなく、処理する配列がまだあるからです。

7
Matt

For-eachループが構文シュガーであるという事実に、答えの一部が隠れていると思います。ポイントは、人々が多くのことをすることをもっと簡単にしたいということです。そして(少なくとも私の経験では)イディオム

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

古いスタイルのコードで常に発生していました。そして、複数のイテレーターで派手なことをしませんでした。

1
Jochen