web-dev-qa-db-ja.com

リストの反復中にJava.util.Listから要素を削除すると、ConcurrentModificationExceptionがスローされますか?

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(String st:li){
        if(st.equalsIgnoreCase("str3"))
            li.remove("str3");
    }
    System.out.println(li);
}

このコードを実行すると、ConcurrentModificationExceptionがスローされます。

リストから指定された要素を削除すると、リストはそのサイズが変更されたことを知らないように見えます。

これがコレクションと要素の削除に関する一般的な問題かどうか疑問に思っていますか?

59
hguser

これが Iterator.remove() メソッドの背後にある目的であり、反復中にコレクションから要素を削除できるようにするためだと思います。

例えば:

Iterator<String> iter = li.iterator();
while(iter.hasNext()){
    if(iter.next().equalsIgnoreCase("str3"))
        iter.remove();
}
88
Paul Blessing

Java Iteratorを使用せずにリストから削除する8つの方法は次のとおりです。

li.removeIf(<predicate>)

つまり.

List<String> li = new ArrayList<String>();
// ...
li.removeIf(st -> !st.equalsIgnoreCase("str3"));
22
bigdev.de

この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。 1つのスレッドがオブジェクトのコントラクトに違反する一連のメソッド呼び出しを発行すると、オブジェクトはこの例外をスローする場合があります。たとえば、フェイルファストイテレータを使用してコレクションを反復処理中にスレッドがコレクションを直接変更する場合、イテレータはこの例外を処理します。

http://download.Oracle.com/javase/1.4.2/docs/api/Java/util/ConcurrentModificationException.html から取得

18
Serhiy

はい、人々はそれに出くわします-問題は、それを反復している間はリストを変更できないことです。私は過去に2つの選択肢を使用しました:

  1. 削除するアイテムのインデックスを追跡し、反復が完了したら削除できます。
  2. または、繰り返しながら保持したいものをすべて新しいリストにコピーし、完了したら古いリストを破棄できます。

これらのオプションは、削除する要素を見つけるためにリストを反復処理する必要があることを前提としています。リスト要素がテスト対象のプロパティを持つ複雑なオブジェクトである場合に便利です。

特定のケースでは、removeAllを使用するだけなので、反復する必要さえありません。 APIをご覧ください こちら 。引数にないものをすべて破棄するretainAllのような気の利いたメソッドもあります。リスト内のオブジェクトがequalsおよびhashcodeを適切に実装する場合はいつでも、remove/retain-likeメソッドを使用できます。アプリ内のインスタンス間の同等性を識別するためにequals/hashcodeに依存できない場合は、自分で削除する必要があります。

6
hvgotcodes

For-eachループで、要素を削除するリストのコピーを直接作成できます。私にとって、それが最も簡単な方法です。このようなもの:

for (String stringIter : new ArrayList<String>(myList)) {
    myList.remove(itemToRemove);
}

それがあなたを助けることを願っています。

2
stakahop

Java 8バージョンに言及する価値があると思います

@Test
public void testListCur() {
    List<String> li = new ArrayList<String>();
    for (int i = 0; i < 10; i++) {
        li.add("str" + i);
    }

    li = li.stream().filter(st -> !st.equalsIgnoreCase("str3")).collect(Collectors.toList());

    System.out.println(li);
}
2
gdogaru

これを試してください(Java 8):

list.removeIf(condition);
2
Taras Melnyk

ArrayListにはmodCountフィールドがあります-コレクション変更のカウント

メソッドiterator()を呼び出すと、新しいオブジェクトItrが作成されます。 expectedModCountフィールドがあります。 expectedModCountフィールドは、modCount値で初期化されます。呼び出すとき

li.remove("str3");

modCountインクリメント。いつイテレータ経由でliへのアクセスを試みますかexpectedModCount == modCount

falseの場合はConcurrentModificationExceptionをスローします

したがって、イテレータを取得し、コレクションが変更された後-イテレータは無効とみなされ、使用できません。

1
gstackoverflow

私はこの問題を手に入れましたが、簡単な方法はhvgotcodesが提供した2番目の方法と同じだと思います。

または、繰り返しながら保持したいものをすべて新しいリストにコピーし、完了したら古いリストを破棄できます。

@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }
    List<String> finalLi = new ArrayList<String>();
    for(String st:li){
        if(st.equalsIgnoreCase("str3")){
            // Do nothing
        } else {
            finalLi.add(st);
        }
    }
    System.out.println(finalLi);
}
1
Envil

私は最高の答えはbigdev.deからだと思いますが、私はそれに何かを追加したいと思います(アイテムがリストから削除された場合、おそらくどこかに何かを記録したいかもしれません):

List<String> list = new ArrayList<>();

list.removeIf(a -> {
                boolean condition = a.equalsIgnoreCase("some condition");
                if(condition)
                    logger.info("Item removed from the list: " + a);
                return condition;
  });
0
rpajaziti

私は別の方法でループしました...

public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(int i=0; i<li.size(); i++)
        if(li.get(i).equalsIgnoreCase("str3"))
            li.remove(i--);

    System.out.println(li);
}
0
Mark Meyers