拡張for文 - java util concurrentmodificationexception null




Collectionを反復し、ループ内での変更(削除)時にConcurrentModificationExceptionを回避する (15)

私たち皆はあなたがこれをすることができないことを知っている:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationExceptionなど...これは明らかに時々動作しますが、必ずしもそうではありません。 具体的なコードは次のとおりです。

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

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

もちろん、これは次のようになります。

Exception in thread "main" java.util.ConcurrentModificationException

...複数のスレッドがそれをやっていないにもかかわらず...とにかく。

この問題に対する最善の解決策は何ですか? この例外をスローせずにループ内のアイテムをコレクションから削除するにはどうすればよいですか?

私はここでも任意のCollectionを使用していget 。必ずしもArrayListである必要はないので、 get依存することはできませget


最良の方法(推奨)は、java.util.Concurrentパッケージの使用です。 このパッケージを使用すると、この例外を簡単に回避できます。 変更されたコードを参照してください

public static void main(String[] args) {
        Collection<Integer> l = new CopyOnWriteArrayList<Integer>();

        for (int i=0; i < 10; ++i) {
            l.add(new Integer(4));
            l.add(new Integer(5));
            l.add(new Integer(6));
        }

        for (Integer i : l) {
            if (i.intValue() == 5) {
                l.remove(i);
            }
        }

        System.out.println(l);
    }

Java 8では、新しいremoveIfメソッドを使用できます。 あなたの例に適用されます:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

forループを使ったClaudiusと同じ答え:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}


Eclipse Collections (以前のGS Collections )では、 removeIf定義されたMutableCollectionが機能します:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Java 8 Lambdaの構文では、次のように書くことができます。

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Java 8のjava.util.CollectionインタフェースにデフォルトのremoveIfメソッドが追加されたため、ここでPredicates.cast()の呼び出しが必要です。

注:私はEclipse Collectionsのコミッターです。


ListIterator使用すると、リスト内の項目を追加または削除できます。 Carオブジェクトのリストがあるとします。

List<Car> cars = ArrayList<>();
// add cars here...

for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
{
   if (<some-condition>)
   { 
      carIterator().remove()
   }
   else if (<some-other-condition>)
   { 
      carIterator().add(aNewCar);
   }
}

これは動作します:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

私は、foreachループが.remove()ための構文的な砂糖であると仮定したので、イテレータを使用すると助けにはならないと思っていましたが、この.remove()機能を提供します。


これは最善の方法ではないかもしれませんが、小規模なケースの大部分については、これは受け入れられるべきです:

"2番目の空の配列を作成し、保持したいものだけを追加する"

私はこれをどこから読んだのか記憶していません...正当な理由で、誰かがそれを見つけて欲しいと願ってこのウィキを作ります。


スレッドセーフコレクションの変更の例:

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}

上記の問題に対する提案があります。 二次的なリストや余分な時間を必要としません。 同じことをするが、別の方法で行う例を見つけてください。

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

これにより同時実行例外が回避されます。


伝統的なforループ

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

前述のようにイテレータを直接使用することもできますし、2番目のコレクションを保持して、削除する各アイテムを新しいコレクションに追加し、最後にremoveAllを追加することもできます。 これにより、for-eachループの型安全性を、メモリ使用量とCPU時間の増加を犠牲にして、引き続き使用することが可能になります(本当に大きなリストや実際に古いコンピュータがない限り、大きな問題にならないはずです)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

既存のリストのコピーを作成し、新しいコピーを繰り返します。

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

私はこの質問がJava 8については古すぎると知っていますが、Java 8を使用している場合はremoveIf()を簡単に使用できます。

Collection<Integer> l = new ArrayList<Integer>();

for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
}

l.removeIf(i -> i.intValue() == 5);

for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

catchは内部のiterator.next()呼び出しをスキップすると、リストから要素を削除した後です。 それはまだ動作します! このようなコードを書くことは提案しませんが、その背後にあるコンセプトを理解するのに役立ちます:-)

乾杯!





collections