web-dev-qa-db-ja.com

ArrayListのforeachループ内に追加するとConcurrentModificationException

Arraylistでforeachループを使用しようとしていますが、それを使用するとエラーが発生しますが、通常のforループを使用すると完全に機能しますが、問題は何ですか?

コードはここにあります:

for (Pair p2 : R) {
    if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), p2.getSecondElm()));
    else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p2.getFirstElm(), p.getSecondElm()));

    // else
    // There are no transitive pairs in R.
}

これは機能しないループであり、これが機能するループです。

for (int i = 0; i < R.size(); i++) {
    if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), R.get(i).getSecondElm()));
    else if ((R.get(i).getSecondElm() == p.getFirstElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (R.get(i).getFirstElm(), p.getSecondElm()));
    //else
    //  There are no transitive pairs in R.
}

foreachループを使用したときに発生するエラーは次のとおりです。

Exception in thread "main" Java.util.ConcurrentModificationException
    at Java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at Java.util.AbstractList$Itr.next(Unknown Source)  
    at set.problem.fourth.PoSet.makeTransitive(PoSet.Java:145)  
    at set.problem.fourth.PoSet.addToR(PoSet.Java:87)
    at set.problem.fourth.PoSetDriver.typicalTesting(PoSetDriver.Java:35)
    at set.problem.fourth.PoSetDriver.main(PoSetDriver.Java:13)
22
hakuna matata

Javaコレクションクラスはフェイルファストです。つまり、一部のスレッドがイテレータを使用してコレクションをトラバースしている間にコレクションが変更されると、iterator.next()ConcurrentModificationExceptionをスローします。

この状況は、マルチスレッド環境とシングルスレッド環境の両方で発生する可能性があります。 - www.javacodegeeks.com

_for/each_ループでListを変更することはできません。これは、実装の詳細としてのIteratorの周りの構文上のシュガーです。 Iteratorを直接使用する場合のみ、.remove()を安全に呼び出すことができます。

Iterator.removeは、反復中にコレクションを変更する唯一の安全な方法です。反復の進行中に基になるコレクションが他の方法で変更された場合の動作は規定されていません。 - Javaコレクションのチュートリアル

_for/each_ループ内で.add()を呼び出すと、内容が変更され、舞台裏で使用されるIteratorがこれを認識し、この例外をスローします。

より微妙な懸念は、2番目にリストする方法である.size().add()を実行するたびに増加するため、最終的に.add() 、これは入力データが何であるかに応じて無限ループを引き起こす可能性があります。これがあなたの望むことかどうかはわかりません。

ソリューション

別のArrayList.add()を作成して、すべての新しいものを作成し、ループの後、元のArrayList.addAll()を使用して結合します2つのリストを一緒に。これにより、何をしようとしているのかが明確になります。つまり、新しく追加したものをすべて追加するときに、それらをすべて処理する意図がない場合です。

2014ソリューション:

単一の共有クラスを変更するのではなく、常にImmutableコレクションクラスを使用して、新しいImmutableコレクションクラスを構築します。これは基本的に私の2012年の回答で述べられていることですが、もっと明確にしたかったのです。

Guavaはこれを非常によくサポートしています。ImmutableList.copyOf()を使用してデータを渡します。

Iterables.filter()を使用して、アイテムを新しいImmutableListにフィルターで除外します。可変の共有状態はなく、同時実行の問題はありません。

36
user177800

内部では、Javaのfor-eachループは、コレクションを走査するためにIteratorを使用します(詳細な説明については、この article を参照してください。)そしてIteratorはConcurrentModificationExceptionをスローします。コレクションを繰り返し処理しているときにコレクションを変更すると、この post を参照してください。

3
Óscar López

問題は、ループの最初の行でR.add()を実行していることです。

最初の状況では、arraylistに対して開いているイテレータがあります。追加を行ってからもう一度イテレートしようとすると、イテレータはデータ構造が自分の下で変更されたことに気づきます。

Forルックの場合、毎回新しい要素を取得するだけで、同時変更の問題はありませんが、要素を追加するとサイズが変化します。

問題を修正するには、おそらく一時的な場所に追加し、ループ後に追加するか、初期データのコピーを作成して元の場所に追加する必要があります。

1
Paul Rubel