web-dev-qa-db-ja.com

ループ内のオブジェクトを削除するときにConcurrentModificationExceptionを回避しながらCollectionを繰り返し処理する

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

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

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

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

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

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

    System.out.println(l);
}

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

Exception in thread "main" Java.util.ConcurrentModificationException

...複数のスレッドがそれを実行していなくても...とにかく。

この問題に対する最良の解決策は何ですか?この例外をスローしないで、ループ内でコレクションから項目を削除する方法を教えてください。

ここでも任意のCollectionを使用していますが、必ずしもArrayListを使用しているわけではないので、getに頼ることはできません。

1116
Claudiu

Iterator.remove() は安全です。次のように使用できます。

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

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

Iterator.remove() は、反復中にコレクションを変更する唯一の安全な方法であることに注意してください。基礎となるコレクションが変更された場合の動作は指定されていません他の方法で反復の進行中。

ソース: docs.Oracle> The Collection Interface


同様に、ListIteratorがあり、addアイテムを追加したい場合は、 ListIterator#add を使用できます。同じ理由で、Iterator#removeを使用できます—それを許可するように設計されています。


あなたの場合、リストから削除しようとしましたが、そのコンテンツを繰り返しながらputMapにしようとする場合、同じ制限が適用されます。

1553
Bill K

これは動作します:

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

Foreachループは繰り返しのための構文上の糖であるので、イテレータを使っても意味がないと思いました...しかしそれはあなたにこの.remove()機能を与えます。

331
Claudiu

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

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

coll.removeIf(i -> i == 5);
181
assylias

質問はすでに答えられている、すなわち最善の方法はイテレータオブジェクトのremoveメソッドを使用することであるので、エラー"Java.util.ConcurrentModificationException"が投げられる場所の詳細に入ります。

すべてのコレクションクラスは、Iteratorインターフェースを実装し、next()remove()hasNext()のようなメソッドを提供するプライベートクラスを持っています。

Nextのコードはこのようになります...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

ここでメソッドcheckForComodificationは次のように実装されています。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

そのため、見てのとおり、明示的にコレクションから要素を削除しようとしたとします。その結果、modCountexpectedModCountとは異なり、例外ConcurrentModificationExceptionが発生します。

40
Ashish

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

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);
}
25
RodeoClown

そのような場合、よくあるトリックは逆方向に進むことでした(そうでしたか?)。

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

それは言った、私はあなたがJava 8でより良い方法を持っていることをとてもうれしく思います。ストリーム上のremoveIfまたはfilter

17
Landei

Claudius と同じ答えでforループを使う:

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

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

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);

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

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

11
Donald Raab

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

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

伝統的な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++;
      }
7
Lluis Felisart

人々は1を主張しています 不可能ではありません foreachループによって繰り返されているコレクションから削除します。私はただ技術的が正しくないことを指摘し、正確に説明します(私はOPの質問がこれを知ることを避けるために非常に進んでいることを知っています)。

    for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
        if (obj.isTouched()) {
            untouchedSet.remove(obj);
            touchedSt.add(obj);
            break;  // this is key to avoiding returning to the foreach
        }
    }

繰り返したColletionから削除できないわけではなく、一度実行した後は繰り返しを続行できないということではありません。したがって、上記のコードのbreakです。

この答えがやや専門的なユースケースであり、私がここから到着した元の thread に適している場合は謝罪します。

7
John

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);
   }
}
2
james.garriss

もう1つの方法は、arrayListのコピーを作成することです。

List<Object> l = ...

List<Object> iterationList = ImmutableList.copyOf(l);

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

}

1
Nestor Milyaev

最善の方法(推奨)は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);
    }
1
jagdish khetre

上記の問題についての提案があります。二次リストも追加の時間も必要ありません。同じことを異なる方法で実行する例を見つけてください。

//"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;
}

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

1

ConcurrentHashMap または ConcurrentLinkedQueue または ConcurrentSkipListMap は、アイテムを削除または追加してもConcurrentModificationExceptionがスローされないため、別のオプションとして使用できます。

1
Yessy

この質問はCollectionだけを想定しており、より具体的にはListを想定していないことを知っています。しかし、実際にList参照で作業しているこの質問を読んでいる人は、ConcurrentModificationExceptionを避けたい場合は、代わりにwhileループを使用してIterator- loopを変更することができます一般的にそれを回避したい場合、または各要素での開始から終了までの停止とは異なるループ順序を達成するために具体的に回避する場合[これはIterator自体が実行できる唯一の順序です]

*更新:traditional-for-loopでも同様のことが達成できることを明確にする以下のコメントを参照してください。

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 1;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i++);

    } else {
        i += 2;
    }
}

そのコードからのConcurrentModificationExceptionはありません。

ループは最初から開始せず、every要素(Iterator自体ではできないと考えています)で停止しません。

FWIWでは、getlistで呼び出されていることも確認できますが、その参照が(より具体的なCollectionタイプのListの代わりに)単にCollectionの場合は実行できませんでした-Listインターフェイスにはgetインターフェイスが含まれますが、Collectionインターフェイスには含まれません。その違いがない場合、list参照は代わりにCollectionになる可能性があります[したがって、技術的にはこのアンサーは接線アンサーではなく直接アンサーになります]。

FWIWW同じコードは、すべての要素で停止から開始するように変更した後でも機能します(Iterator順序と同様)。

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}
0
cellepo

1つの解決策は、ConcurrentModificationExceptionまたはIndexOutOfBoundsExceptionを回避するためにリストを回転し、最初の要素を削除することです。

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
0
Rahul Vala
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

内部のiterator.next()呼び出しをスキップした場合は、リストから要素が削除された後にキャッチが発生します。それはまだうまくいきます!私はこのようなコードを書くことを提案しませんがそれはその背後にある概念を理解するのに役立ちます:-)

乾杯!

0

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

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();
            }
        }
    }
}
0
Yazon2006

私はこの質問が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);
0
pedram bashiri

ArrayList:remove(int index) - if(indexが最後の要素の位置)の場合、System.arraycopy()なしで避け、これには時間がかかりません。

listの要素も減少するので、arraycopy時間は増加します(indexが減少する)。

最も効果的な削除方法は、要素を降順に削除することです。while(list.size()>0)list.remove(list.size()-1); //はO(1) while(list.size()>0)list.remove(0); //はOを取ります(factorial(n))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • インデックスループの場合:1090ミリ秒
  • descインデックスの場合: 519 msec ---最高
  • イテレータの場合:1043ミリ秒
0
Nurlan