ループでオブジェクトを削除するときにConcurrentModificationExceptionを回避して、コレクションを反復処理する


1194

私たちは皆、あなたが次の理由で次のことができないことを知っていますConcurrentModificationException

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

しかし、これは明らかに機能することもありますが、常に機能するとは限りません。ここにいくつかの特定のコードがあります:

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


読者への注意:docs.oracle.com/javase/tutorial/collections/interfaces/…を読んでください。やりたいことを簡単に実現できる場合があります。
GKFX 2017

回答:


1601

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>コレクションインターフェース


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


あなたの場合、リストから削除しようとしputましたMapが、コンテンツを反復しながらしばらくしようとした場合にも同じ制限が適用されます。


19
現在の反復で返された要素以外の要素を削除したい場合はどうなりますか?
オイゲン

2
イテレータで.removeを使用する必要があり、それは現在の要素を削除することしかできないため、いいえ:)
Bill K

1
これは、(少なくとも私の場合)ConcurrentLinkedDequeまたはCopyOnWriteArrayListを使用する場合と比べて遅いことに注意してください
Dan

1
iterator.next()呼び出しをforループに入れることはできませんか?そうでない場合、誰かが理由を説明できますか?
ブレイク

1
@GonenI不変ではないコレクションのすべてのイテレータに対して実装されます。List.add同じ意味でも「オプション」ですが、リストに追加するのが「安全でない」とは言えません。
Radiodef

345

これは機能します:

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

foreachループは反復のための構文シュガーであるため、反復子を使用しても役に立たないと想定しましたが、この.remove()機能は提供されます。


43
foreachループ反復のための構文糖です。ただし、指摘したように、イテレータでremoveを呼び出す必要があります。foreachではアクセスできません。したがって、(実際に
内部で

36
コンテキストでiter.remove()を使用するためのコード例の+1。BillKの回答には[直接]ありません。
編集済み

202

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

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

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

3
おおお!Java 8または9の何かが役立つことを願っていました。これはまだ私にはかなり冗長に思えますが、私はそれでも好きです。
James T Snell 2015

この場合にも、equals()の実装は推奨されますか?
Anmol Gupta

ちなみにremoveIf使用Iteratorしてwhileループ。あなたはそれをjava 8で見ることができますjava.util.Collection.java
omerhakanbilici

3
@omerhakanbilici一部の実装ArrayListでは、パフォーマンス上の理由でオーバーライドされます。あなたが参照しているものはデフォルトの実装のみです。
Didier L

@AnmolGupta:いいえ、equalsここではまったく使用されていないため、実装する必要はありません。(もちろん、equalsテストで使用する場合は、希望
どおりに

42

質問はすでに回答済みです。つまり、イテレータオブジェクトのremoveメソッドを使用するのが最善の方法なので、エラー"java.util.ConcurrentModificationException"がスローされる場所の詳細に行きます。

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

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

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

ご覧のとおり、コレクションから要素を明示的に削除しようとすると、その結果、とmodCountは異なりexpectedModCount、例外が発生しますConcurrentModificationException


とても興味深い。ありがとうございました!私は自分でremove()を呼び出さないことがよくあります。代わりに、コレクションを反復処理した後にコレクションをクリアすることを好みます。それは良いパターンだと言っているのではなく、私が最近やっていることだけです。
James T Snell 2015

26

あなたが言及したようにイテレータを直接使用するか、2番目のコレクションを保持して、削除する各項目を新しいコレクションに追加し、最後にremoveAllを追加します。これにより、メモリ使用量とCPU時間の増加を犠牲にして、for-eachループのタイプセーフを使い続けることができます(本当に、本当に大きなリストまたは本当に古いコンピューターでない限り、大きな問題にはなりません)。

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

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

7
これは私が通常行うことですが、明示的なイテレータは私が感じるよりエレガントなソリューションです。
Claudiu

1
イテレータで他に何も実行していない限り、十分に公平です-公開すると、ループごとに.next()を2回呼び出すなどの処理が簡単になります。大きな問題ではありませんが、実行すると問題が発生する可能性があります。エントリを削除するためにリストを実行するよりも複雑なこと。
ロデオクラウン2008年

@RodeoClown:元の質問では、Claudiuはイテレーターではなくコレクションから削除しています。
matt b

1
イテレータから削除すると、基になるコレクションから削除されます...しかし、最後のコメントで私が言っていたのは、イテレータを使用してループ内で削除を検索するだけではなく、正しいデータを処理するよりも複雑なことを行っている場合、エラーを簡単に作成できます。
ロデオクラウン2008年

不要な単純な削除値であり、ループがその1つだけを実行している場合は、イテレータを直接使用して.remove()を呼び出しても問題ありません。
ロデオクラウン2008年

17

そのような場合、一般的なトリックは(そうでしたか?)後退することです:

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

そうは言っても、Java 8で、removeIfまたはfilterストリーム上など、より良い方法があることは私にとって非常に嬉しいことです。


2
これは良いトリックです。しかし、それはセットのようなインデックスのないコレクションでは機能せず、たとえばリンクされたリストでは本当に遅くなります。
Claudiu 2014

@Claudiuはい、これは間違いなくArrayListsまたは同様のコレクションのためだけです。
Landei 2014

私はArrayListを使用していますが、これは完全に機能しました。
StarSweeper 2018

2
インデックスは素晴らしいです。それがそれほど一般的であるなら、なぜあなたは使わないのですfor(int i = l.size(); i-->0;) {か?
ジョン

16

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

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

12

Eclipseのコレクション、法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のインターフェースにPredicates.cast()デフォルトのremoveIfメソッドが追加されたため、ここでの呼び出しが必要ですjava.util.Collection

注:私はEclipseコレクションのコミッターです。


10

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

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

19
コピーを作成することは、リソースの浪費のように聞こえます。
Antzi 2013

3
@Antziこれは、リストのサイズと内部のオブジェクトの密度に依存します。それでも価値のある有効なソリューションです。
mre 2016年

私はこの方法を使用しています。もう少しリソースが必要ですが、はるかに柔軟で明確です。
Tao Zhang、

これは、ループ自体の内部でオブジェクトを削除するつもりはないが、他のスレッドからは「ランダムに」削除される場合(たとえば、データを更新するネットワーク操作)に適しています。これらのコピーを頻繁に
javase

8

人々は、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上記のコードで。

この回答がやや専門的なユースケースであり、私がここに到着した元のスレッドにより適している場合、お詫び申し上げます。これは、このスレッドの複製としてマークされ(このスレッドは微妙に見えます)、ロックされています。


8

従来の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++;   
}

ああ、それは本当に例外をスローする拡張された forループです。
cellepo 2018

FWIW-変更後も同じコードがi++ループ本体内ではなくループガード内でインクリメントするように機能します。
cellepo 2018

修正^:これは、i++インクリメントが条件付きではなかった場合です-だから、あなたが本文でそれを行うのはそのためです:)
cellepo

2

Aを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);
   }
}

ListIteratorインターフェースの追加メソッド(Iteratorの拡張)は興味深いものです-特にそのpreviousメソッド。
cellepo

1

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

//"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
この質問では、OPは必ずしも使用する必要はなくArrayList、したがってに依存することはできないと明確に述べていget()ます。それ以外の場合は、おそらく良いアプローチです。
kaskelotti 14

(明確化^)OPは任意インターフェースを使用していますCollection- Collectionインターフェースは含まれていませんget。(ただし、FWIW Listインターフェイスには「get」が含まれています)。
cellepo

ここでも、をwhileループするための個別のより詳細な回答を追加しましたList。しかし、この回答が最初に来たので、+ 1してください。
cellepo

1

ConcurrentHashMapConcurrentLinkedQueue、またはConcurrentSkipListMapは、アイテムを削除または追加しても、ConcurrentModificationExceptionをスローしないため、別のオプションになる場合があります。


うん、それらはすべてjava.util.concurrentパッケージに入っていることに注意してください。そのパッケージからの他のいくつかの類似/共通使用ケースクラスはCopyOnWriteArrayList CopyOnWriteArraySet [これらに限定されません]です。
cellepo

実際、私はこれらのデータ構造Objectが回避 するもののConcurrentModificationException拡張された -for-loopでそれらを使用するとインデックス作成の問題が発生する可能性があることを学びました(例:要素のスキップなどIndexOutOfBoundsException
cellepo

1

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

1

別の方法は、arrayListのコピーを作成することです。

List<Object> l = ...

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

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

注:iではありませんindexが、代わりにオブジェクト。多分それobjはよりふさわしいだろうと思います。
ラッキードナルド

1

最良の方法(推奨)は、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);
}

0

場合ArrayListを:削除(int型のインデックス) - (インデックスが最後の要素の位置である)、それはせずに回避した場合System.arraycopy()や、このための時間がないかかります。

リストの要素も減少するので、(インデックスが減少する)if arraycopy時間は増加します!

最も効果的な削除方法は、その要素を降順で削除することです。// 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ミリ秒---最高
  • イテレータの場合:1043ミリ秒

0
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

私はこの質問が単にCollection特定のものではなく、単に仮定していることを知っていますList。しかし、実際にList参照で作業しているこの質問を読んでいる人にとっては、回避ConcurrentModificationExceptionwhileたいIterator場合(一般にそれを回避したい場合、または具体的にそれを回避したい場合)に-loop を使用して回避できます(その中で変更します)。開始から終了までの各要素での停止とは異なるループ順序(これは、Iteratorそれ自体が実行できる唯一の順序であると私は信じています):

*更新:従来の -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はありません。

ループは最初から始まらず、すべての要素で止まらないことがわかります(私はIteratorそれ自体はできないと思います)。

FWIW getが呼び出されていることもわかります。list参照がCollection(より具体的なList-typeの代わりに)だけの場合は実行できませんでした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;
    }
}

ただし、これでも、削除するインデックスの非常に慎重な計算が必要です。
OneCricketeer 2018

また、これはこの回答のより詳細な説明ですstackoverflow.com/a/43441822/2308683
OneCricketeer

知っておきたいこと-ありがとう!そのほかの回答は、私はそれがだと理解を助け強化 -for-ループ投げるだろうConcurrentModificationException、しかしではない伝統的な(他の回答が使用する)-for-ループ-その前に実現していないが、私が誤って(私はこの回答を書き込むことが動機とされた理由でありますその後、例外をスローするのはすべて forループであると考えられていました)。
cellepo 2018

0

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

これを試してください(リスト内のに等しいすべての要素を削除しますi):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}

0

再帰も使用できます

Javaでの再帰は、メソッドがそれ自体を継続的に呼び出すプロセスです。自身を呼び出すJavaのメソッドは、再帰的メソッドと呼ばれます。


-2

これは最良の方法ではないかもしれませんが、ほとんどの場合、これは許容できるはずです。

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

私はこれをどこから読んだか覚えていません...正直なところ、このウィキは誰かがそれを見つけられるように、または私が値するはずの担当者を獲得しないために作ります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.