1. ホーム
  2. java

[解決済み] Javaでリストを反復処理する方法

2022-03-15 22:36:21

質問

Java言語の初心者なので、リスト(または他のコレクション)を反復処理するすべての方法(または少なくとも病的でない方法)と、それぞれの利点または欠点に慣れようとしています。

ある List<E> list オブジェクトのすべての要素をループする方法として、私は以下の方法を知っています。

基本的な に対して ループ (もちろん、同等の while / do while ループも同様)

// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
    E element = list.get(i);
    // 1 - can call methods of element
    // 2 - can use 'i' to make index-based calls to methods of list

    // ...
}

注意:@amarseillan が指摘したように、この形式はあまり良い選択ではありません。 を反復するために List の実際の実装は は get メソッドを使用する場合ほど効率的でない可能性があります。 Iterator . 例えば LinkedList の実装は、すべての i番目の要素を得るためにiの前にある要素。

上の例では List の実装は 将来の繰り返しをより効率的にするために、その場所を保存しておく必要があります。 そのため ArrayList の複雑さとコストは非常に低いので、あまり重要ではありません。 get は定数時間(O(1))であるのに対して LinkedList は、リストのサイズに比例する(O(n))。

の計算量の詳細については、組み込みの Collections の実装は、以下を参照してください。 この質問 .

強化された ループ用 (うまく説明されています この質問で )

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

イテレータ

for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list

    // ...
}

リストイテレータ

for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list
    // 3 - can use iter.add(...) to insert a new element into the list
    //     between element and iter->next()
    // 4 - can use iter.set(...) to replace the current element

    // ...
}

機能的なJava

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach , ストリーム.forEach , ...

(Java 8 の Stream API の map メソッド (@i_am_zero さんの回答参照) 。)

を実装したJava 8のコレクションクラスでは Iterable (例えば、すべての List が追加されました。 forEach メソッドの代わりに使用することができます。 for ループステートメント を実証しました。(以下は 別の質問 は、良い比較を提供しています)。

Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
//     (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
//     being performed with each item.

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).

他に方法があるとすれば、どのようなものがありますか?

(ちなみに、私の興味は、以下のような願望から来るものでは全くありません。 パフォーマンスの最適化 開発者としてどんなフォームがあるのか知りたいだけなんです)

どのように解決するのですか?

ループの3つの形態は、ほぼ同じです。強化された for のループになります。

for (E element : list) {
    . . .
}

Java言語仕様 , 同一 でのイテレータの明示的な使用と実質的に同じです。 for ループを使用します。3番目のケースでは、リストの内容を変更するには、現在の要素を削除する必要があります。 remove というメソッドがあります。インデックスベースのイテレータでは、どのような方法でリストを変更してもかまいません。しかし、現在のインデックスより前にある要素を追加したり削除したりすると、ループが要素を読み飛ばしたり、同じ要素を何度も処理したりする危険性があります。そのような変更をするときは、ループのインデックスを適切に調整する必要があります。

すべての場合において element は実際のリスト要素への参照です。どの反復処理メソッドも、リスト内の何かのコピーを作成することはありません。の内部状態への変更は element は、常にリスト上の対応する要素の内部状態を見ることができます。

基本的に、リストを反復処理する方法は、インデックスを使用する方法とイテレータを使用する方法の2つだけです。拡張されたforループは、明示的にイテレータを定義する退屈さを避けるためにJava 5で導入された構文的なショートカットに過ぎません。どちらの方法でも、基本的には for , while または do while のブロックがありますが、どれも同じこと(というか、2つのこと)に帰結します。

EDIT: @iX3 がコメントで指摘しているように、このような場合は ListIterator を使用して、反復処理中にリストの現在の要素を設定することができます。そのためには List#listIterator() ではなく List#iterator() を使用してループ変数を初期化します (当然ながら、ループ変数には ListIterator ではなく Iterator ).