2015-09-15 4 views
8

someMapからすべてのアイテムを削除します。どのキーもsomeListに存在しません。私のコードを見てください:マップキーでストリームを使用するConcurrentModificationException

someMap.keySet().stream().filter(v -> !someList.contains(v)).forEach(someMap::remove); 

私はjava.util.ConcurrentModificationExceptionを受け取ります。どうして?ストリームが並列ではありません。これを行う最もエレガントな方法は何ですか?

答えて

15

@Eranよりよいこの問題を解決するための方法をすでにexplained。なぜConcurrentModificationExceptionが発生するのか説明します。

ConcurrentModificationExceptionは、ストリームソースを変更するために発生します。 MapHashMapまたはTreeMapまたは他の非並行マップになる可能性があります。それがHashMapだとします。すべてのストリームはSpliteratorによってバックアップされています。 spliteratorは何IMMUTABLECONCURRENT特性を持っていない場合は、その後、ドキュメントが言うように:構造的な干渉が検出された場合

Spliteratorを結合した後、ベストエフォート方式で、ConcurrentModificationExceptionをスローする必要があります。これを行うスプリッターは、フェール・ファーストと呼ばれます。 (このSetを変更することができるので)

だからHashMap.keySet().spliterator()IMMUTABLEではなく、ないCONCURRENT(同時更新はHashMapのために安全ではありません)。したがって、それはただ同時に変化を検出し、スプライテータの文書化規定に従ってConcurrentModificationExceptionをスローします。また

HashMapドキュメント引用それだけの価値:このクラスの「コレクションビューメソッド」のすべてで返さ

イテレータをしているフェイルファストを:マップが構造的にイテレータの後に任意の時点で変更された場合イテレータ自身のremoveメソッド以外の方法でイテレータがConcurrentModificationExceptionをスローします。したがって、同時の変更に直面して、イテレータは、未定義の時間に任意の非決定論的な動作を将来的に危険にさらすのではなく、迅速かつきれいに失敗します。

イテレータのフェイル・ファーストの動作は、一般的に言えば、非同期同時変更の存在下では何の保証もできないため、保証できません。フェイル・ファースト・イテレータはベスト・エフォート・ベースでConcurrentModificationExceptionをスローします。したがって、この例外に依存するプログラムを記述することは間違いです。イテレータのフェイル・ファーストの動作は、バグを検出するためにのみ使用してください。

反復子についてのみ述べていますが、私はスプライテータにとっても同じだと考えています。

+0

これは最善の答えだと思いますが、編集して@Eranが述べた解決策を追加することができます。それは、将来同じ問題を抱える人にとっては100%満足するでしょう。 – jaskmar

+1

@MariuszJaskółka、Eranの回答もここにあり、他の人も同様にそれを見る可能性が高いです。それは正しいと私はそれをupvoted。私は彼のソリューションへの参照を追加することができます。 –

8

これには、Stream APIは必要ありません。 keySetretainAllを使用してください。 keySet()によって返されたSetの変更は、元のMapに反映されます。

someMap.keySet().retainAll(someList); 
+0

OK、私の2番目の質問には良い答えです。しかし、なぜ 'java.util.ConcurrentModificationException'が発生するのかまだ分かりません。 – jaskmar

3

あなたのストリームのコールは(論理的)と同じことをやっている:

for (K k : someMap.keySet()) { 
    if (!someList.contains(k)) { 
     someMap.remove(k); 
    } 
} 

これを実行する場合、あなたはそれがあなたと同じ時間にマップを変更しているので、それは、ConcurrentModificationExceptionを投げるでしょうそれを反復しています。あなたはdocsを見ている場合は、以下のことに気づくでしょう:

(注)この例外は常にオブジェクトが同時に別のスレッドによって変更されたことを示すものではありません。 1つのスレッドがオブジェクトの契約に違反する一連のメソッド呼び出しを発行すると、オブジェクトはこの例外をスローする可能性があります。たとえば、スレッドがフェイル・ファースト・イテレータを使用してコレクションを反復している間に、スレッドがコレクションを直接変更した場合、イテレータはこの例外をスローします。

これはあなたがやっていることですが、使用しているマップ実装にはフェイル・ファースト・イテレーターが含まれているため、この例外がスローされています。

一つの可能​​な選択肢は、直接イテレータを使用してアイテムを削除することです:

for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext();) { 
    K next = ks.next(); 
    if (!someList.contains(k)) { 
     ks.remove(); 
    } 
} 
関連する問題