Javaでリストを反復処理する方法


576

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

List<E> listオブジェクトがあれば、すべての要素をループする次の方法を知っています。

基本的なfor ループ(もちろん、同等の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メソッドの実際の実装はを使用する場合ほど効率的でない可能性があるため、このフォームはs を反復する場合には適していませんIterator。たとえば、LinkedList実装はi番目の要素を取得するために、iに先行するすべての要素をトラバースする必要があります。

上記の例では、List実装が「その場所を保存」して将来の反復をより効率的にする方法はありません。の場合、ArrayList複雑さ/コストgetは一定の時間(O(1))であるため、実際には問題ではありませんが、aのLinkedList場合、リストのサイズ(O(n))に比例します。

組み込みCollections実装の計算の複雑さの詳細については、この質問を確認してください。

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

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

    // ...
}

ListIterator

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.forEachStream.forEach、...

(Java 8のStream APIからのマップメソッド(@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).

他にどのような方法がありますか?

(ちなみに、私の興味は、パフォーマンス最適化したいという欲求にまったく由来するものではありません。開発者として私が利用できるフォームを知りたいだけです。)


1
これらは病理学的ではないものですが、コレクションを処理するためにいくつかの関数型ライブラリのいずれかを使用することもできます。
デイブニュートン

List具体的にはインターフェイスについての質問ですか?
Sotirios Delimanolis 2013

@SotiriosDelimanolis、すべての意図と目的、はい<code> List </ code>に固有ですが、たとえば<code> Collection </ code>を操作する他の興味深い方法がある場合は、それらを知りたい。
iX3 2013

@DaveNewton、アイデアをありがとう。私はそのようなものを使用したことがありません。私の編集した質問を見て、あなたの意味が理解できたかどうかをお知らせください。
iX3 2013

回答:


265

ループの3つの形式はほぼ同じです。拡張forループ:

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

Java言語仕様によれば、従来のループでイテレータを明示的に使用するのと実質同じですfor。3番目のケースでは、現在の要素を削除することによってリストの内容を変更できるだけremoveであり、イテレータ自体のメソッドを使用してそれを行う場合に限られます。インデックスベースの反復では、リストを自由に変更できます。ただし、現在のインデックスの前にある要素を追加または削除すると、ループで要素がスキップされるか、同じ要素が複数回処理される危険性があります。このような変更を行う場合は、ループインデックスを適切に調整する必要があります。

すべての場合において、elementは実際のリスト要素への参照です。どの反復メソッドも、リスト内のコピーを作成しません。の内部状態への変更elementは、リストの対応する要素の内部状態で常に確認されます。

基本的に、リストを反復する方法は2つしかありません。インデックスを使用する方法と、イテレータを使用する方法です。拡張されたforループは、反復子を明示的に定義する面倒な作業を回避するためにJava 5に導入された構文上のショートカットにすぎません。どちらのスタイルでもforwhiledo whileブロックを使用して、本質的に些細なバリエーションを思い付くことができますが、それらはすべて同じもの(または2つ)に要約されます。

編集:@ iX3がコメントで指摘しているように、繰り返しを行うときにaを使用しListIteratorてリストの現在の要素を設定できます。ループ変数を初期化するList#listIterator()代わりにを使用する必要がありList#iterator()ます(これは、明らかに、ListIteratorではなくを宣言する必要がありますIterator)。


はい、ありがとうございます。ただし、イテレータが実際に(コピーではなく)実際の要素への参照を返す場合、それを使用してリストの値を変更するにはどうすればよいですか?私は、e = iterator.next()then を使用する場合、値を取得した実際のストアを変更するのではなく、e = somethingElseオブジェクトeが参照しているものを変更するだけでiterator.next()あると考えています。
iX3 2013

@ iX3-インデックスベースの反復についても同様です。新しいオブジェクトを割り当ててもe、リストの内容は変わりません。電話する必要がありますlist.set(index, thing)。(例:)の内容は変更できますが、繰り返し処理中にリストに格納される要素を変更するには、インデックスベースの反復を使用する必要があります。ee.setSomething(newValue)
テッドホップ2013

説明ありがとうございます。私はあなたが今言っていることを理解し、それに応じて私の質問/コメントを更新すると思います。それ自体「コピー」はありませんが、言語設計のため、コンテンツを変更するには、割り当てがポインターを変更するだけなので、のメソッドのe1つを呼び出す必要eがあります(私のCを許してください)。
iX3 2013

だから、のような不変の種類のリストのIntegerString、使用して内容を変更することはできないだろうfor-eachか、Iterator方法を-要素に置き換えるために、リストオブジェクト自体を操作する必要があります。そうですか?
iX3 2013

@ iX3-正解。3番目の形式(明示的なイテレータ)で要素を削除できますが、リストに要素を追加または置換できるのは、インデックスベースの反復を使用する場合のみです。
テッドホップ2013

46

質問にリストされている各種類の例:

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List<Integer> numbers = new ArrayList<Integer>();

        // populates list with initial values
        for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
            numbers.add(i);
        printList(numbers);         // 0,1,2,3,4,5,6,7

        // replaces each element with twice its value
        for (int index=0; index < numbers.size(); index++) {
            numbers.set(index, numbers.get(index)*2); 
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // does nothing because list is not being changed
        for (Integer number : numbers) {
            number++; // number = new Integer(number+1);
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14  

        // same as above -- just different syntax
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            number++;
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // ListIterator<?> provides an "add" method to insert elements
        // between the current element and the cursor
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.add(number+1);     // insert a number right before this
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        // Iterator<?> provides a "remove" method to delete elements
        // between the current element and the cursor
        for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            if (number % 2 == 0)    // if number is even 
                iter.remove();      // remove it from the collection
        }
        printList(numbers);         // 1,3,5,7,9,11,13,15

        // ListIterator<?> provides a "set" method to replace elements
        for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.set(number/2);     // divide each element by 2
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7
     }

     public static void printList(List<Integer> numbers) {
        StringBuilder sb = new StringBuilder();
        for (Integer number : numbers) {
            sb.append(number);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length()-1); // remove trailing comma
        System.out.println(sb.toString());
     }
}

23

リストの実装がわからないため、基本ループはお勧めしません。

それがLinkedListの場合、への各呼び出し

list.get(i)

リストを繰り返し処理し、N ^ 2時間の複雑さをもたらします。


1
正しい; それは良い点です。質問の例を更新します。
iX3 2015

この投稿によると、あなたのステートメントは正しくありません:for-eachループとイテレータのどちらがより効率的ですか?
Hibbem

5
その投稿で私が読んだのはまさに私が言ったことです...あなたは正確にどの部分を読んでいますか?
アマルセイラン2016年

20

JDK8スタイルのイテレーション:

public class IterationDemo {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("element " + elem));
    }
}

おかげで、私は最終的にJava 8ソリューションで一番上のリストを更新するつもりです。
iX3 2014

1
@ eugene82このアプローチははるかに効率的ですか?
nazar_art 2014

1
@nazar_artおそらく非常に大規模なコレクションでparallelStreamを使用しない限り、他の点で大きな改善は見られません。本当に、冗長性のみを修復するという意味でより効率的です。
ローグ

7

Javaの8我々は、コレクションクラスを反復する複数の方法があります。

Iterable forEachの使用

実装するコレクションIterable(すべてのリストなど)にforEachメソッドが追加されました。Java 8で導入されたメソッド参照を使用できます。

Arrays.asList(1,2,3,4).forEach(System.out::println);

ストリームforEachおよびforEachOrderedの使用

また、次のようにStreamを使用してリストを反復することもできます。

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);

の動作は明示的に非決定的であるので、優先forEachOrderedする必要があります。ストリームが定義された遭遇順序を持っている場合、はこのストリームの各要素に対してアクションを実行するので、ストリームの遭遇順序です。したがって、forEachは順序が保持されることを保証しません。forEachforEachforEachOrdered

ストリームの利点は、必要に応じて並列ストリームを利用できることです。目的が注文に関係なくアイテムを印刷することだけである場合は、並列ストリームを次のように使用できます。

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);

5

私はあなたが病理学的であると考えるものを知りません、しかしあなたがこれまで見たことのないいくつかの選択肢を提供させてください:

List<E> sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

またはその再帰バージョン:

void visit(List<E> list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

また、古典の再帰バージョンfor(int i=0...

void visit(List<E> list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

あなたが「Javaにやや新しい」ので、これは興味深いかもしれません。


1
言及していただきありがとうございます。subListメソッドを見たことがありません。はい、多分難読化コンテストを除いてこれを使用することが有利である状況を私は知りませんので、私はそれを病的であると考えます。
iX3 2013

3
OK。「病理学的」と呼ばれるのは好きではないので、ここで1つの説明を示します。ツリー内のパスを追跡または追跡するときに使用しました。もう一つ?一部のLispプログラムをJavaに変換している間、私はそれらにLispの精神を失わせたくなく、同じようにしました。これらが有効で、病的ではない用途であると思われる場合は、コメントを賛成してください。グループハグが必要です!!! :-)
マリオロッシ

ツリー内のパスはリストとは異なりませんか?ところで、私はあなたや誰かを「病理学的」と呼ぶつもりはありませんでした。私の質問に対するいくつかの回答が、理解できないほど非現実的であるか、優れたソフトウェアエンジニアリングの実践において価値がある可能性が非常に低いことを意味します。
iX3 2013

@ iX3心配しないでください。私はただ冗談を言っていた。パスはリストです:「ルートから開始」(明白)、「2番目の子に移動」、「1番目の子に移動」、「4番目の子に移動」。"やめる"。または略して[2,1,4]。これはリストです。
マリオロッシ

リストのコピーを作成して使用したいと思いますwhile(!copyList.isEmpty()){ E e = copyList.remove(0); ... }。それは最初のバージョンより効果的です;)。
AxelH 2017


0

逆方向検索には、以下を使用する必要があります。

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

位置を知りたい場合は、iterator.previousIndex()を使用してください。また、リスト内の2つの位置を比較する内部ループの記述にも役立ちます(反復子は等しくありません)。


0

そう、多くの選択肢がリストされています。最も簡単でクリーンな方法はfor、以下のように拡張されたステートメントを使用することです。Expression反復可能であるいくつかのタイプのものです。

for ( FormalParameter : Expression ) Statement

たとえば、List <String> IDを反復処理するには、単純にそうすることができます。

for (String str : ids) {
    // Do something
}

3
彼は質問で説明された方法(あなたが言及したものを含む)以外に他の方法があるかどうか尋ねています!
ericbn 2014

0

では、メソッドwith をjava 8使用してリストを反復できます。 List.forEach()lambda expression

import java.util.ArrayList;
import java.util.List;

public class TestA {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}

涼しい。あなたは、これは異なる方法を説明できるeugene82の答えi_am_zeroの答え
iX3

@ iX3ああ。回答リスト全体を読みませんでした。参考にしようとしていました。答えを削除してもよろしいですか?
検索結果Web結果Pi

私には関係ありませんが、他の手法と比較して対照することで改善できるかもしれません。あるいは、それが新しいテクニックを示さないが、他の人へのリファレンスとしてまだ役立つかもしれないと思うなら、おそらくそれを説明するメモを追加することができます。
iX3 2018年

-2

whileループともう少しコードを使用して、最初と3番目の例をいつでも切り替えることができます。これにより、do-whileを使用できるという利点が得られます。

int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

もちろん、list.size()が0を返した場合、常に少なくとも1回は実行されるため、このようなことはNullPointerExceptionを引き起こす可能性があります。これは、属性/メソッドを使用する前に要素がnullかどうかをテストすることで修正できます。それでも、forループを使用する方がずっと簡単で簡単です。


確かに...それは技術的に異なると思いますが、動作が異なるのは、リストが空の場合だけですよね?
iX3 2013

@ ix3はい、その場合、動作はさらに悪くなります。私はこれを良い代替とは呼びません。
CPerkins 2013

確かに、しかし公平に言えば、この質問は「良い」とは何かについてではなく、「可能な」とは何かについての質問なので、私はこの回答に感謝します。
iX3 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.