1. 序章

この記事では、ConcurrentModificationExceptionクラスを見ていきます。

まず、それがどのように機能するかを説明し、次にそれをトリガーするためのテストを使用してそれを証明します。

最後に、実際の例を使用していくつかの回避策を試してみます。

2. ConcurrentModificationExceptionのトリガー

基本的に、 ConcurrentModificationException に慣れている反復しているものが変更された場合、フェイルファストします。 簡単なテストでこれを証明しましょう:

@Test(expected = ConcurrentModificationException.class)
public void whilstRemovingDuringIteration_shouldThrowException() throws InterruptedException {

    List<Integer> integers = newArrayList(1, 2, 3);

    for (Integer integer : integers) {
        integers.remove(1);
    }
}

ご覧のとおり、反復を終了する前に、要素を削除しています。 それが例外の引き金になります。

3. ソリューション

場合によっては、反復中にコレクションから要素を実際に削除したいことがあります。 この場合、いくつかの解決策があります。

3.1. イテレータを直接使用する

for-each ループは、舞台裏で Iterator を使用しますが、それほど冗長ではありません。 ただし、以前のテストをリファクタリングして、 イテレータ、 次のような追加のメソッドにアクセスできます削除する()。 代わりに、このメソッドを使用してリストを変更してみましょう。

for (Iterator<Integer> iterator = integers.iterator(); iterator.hasNext();) {
    Integer integer = iterator.next();
    if(integer == 2) {
        iterator.remove();
    }
}

ここで、例外がないことに気付くでしょう。 この理由は、 削除する() メソッドは発生しません ConcurrentModificationException。 繰り返しながら電話しても安全です。

3.2. 反復中に削除されない

for-each ループを維持したい場合は、それが可能です。 要素を削除する前に、反復後まで待つ必要があるだけです。 繰り返しながら、削除したいものをtoRemoveリストに追加してこれを試してみましょう。

List<Integer> integers = newArrayList(1, 2, 3);
List<Integer> toRemove = newArrayList();

for (Integer integer : integers) {
    if(integer == 2) {
        toRemove.add(integer);
    }
}
integers.removeAll(toRemove);

assertThat(integers).containsExactly(1, 3);

これは、問題を回避するためのもう1つの効果的な方法です。

3.3. removeIf()を使用する

Java 8では、 removeIf()メソッドがCollectionインターフェースに導入されました。 これは、私たちがそれを使って作業している場合、関数型プログラミングのアイデアを使用して同じ結果を再び達成できることを意味します。

List<Integer> integers = newArrayList(1, 2, 3);

integers.removeIf(i -> i == 2);

assertThat(integers).containsExactly(1, 3);

この宣言型のスタイルは、冗長性を最小限に抑えます。 ただし、ユースケースによっては、他の方法の方が便利な場合があります。

3.4. ストリームを使用したフィルタリング

機能的/宣言型プログラミングの世界に飛び込むとき、コレクションの変更を忘れることができます。代わりに、実際に処理する必要のある要素に焦点を当てることができます。

Collection<Integer> integers = newArrayList(1, 2, 3);

List<String> collected = integers
  .stream()
  .filter(i -> i != 2)
  .map(Object::toString)
  .collect(toList());

assertThat(collected).containsExactly("1", "3");

前の例とは逆に、除外ではなく含める要素を決定するための述語を提供しました。 利点は、削除と並行して他の機能を連鎖させることができることです。 この例では、機能的な map()、を使用していますが、必要に応じてさらに多くの操作を使用できます。

4. 結論

この記事では、反復中にコレクションからアイテムを削除する場合に発生する可能性のある問題を示し、問題を解消するためのいくつかの解決策も提供しました。

これらの例の実装は、GitHubにあります。 これはMavenプロジェクトなので、そのまま実行するのは簡単です。