1. 導入:なぜこのエラーが発生するのか?
Javaでコレクション(ListやSetなど)を操作しているとき、「ConcurrentModificationException」というエラーに遭遇したことはありませんか?これは、プログラムが実行中にコレクションの構造が予期せず変更されたことを検知した際に発生する例外です。なぜJavaはこのような厳しいチェックを行うのでしょうか。それは、データの不整合を防ぎ、プログラムの安定性を保つためです。この「Fail-fast(失敗を即座に通知する)」という仕組みを理解することは、堅牢なアプリケーションを作るための第一歩です。
2. 基礎知識:イテレータとFail-fastとは
「イテレータ」とは、コレクション内の要素を一つずつ順番に取り出すための仕組みです。Javaの多くのコレクション(ArrayListやHashSetなど)は、内部的に「modCount(変更回数)」というカウンタを持っています。
イテレータが作成される際、その時点でのmodCountがイテレータに記録されます。イテレータが次の要素に進むたびに、現在のmodCountと記録された値を比較します。もしコレクションの要素が追加・削除されてmodCountが変化していれば、イテレータは「構造が変更された!」と判断し、即座にConcurrentModificationExceptionをスローします。これがFail-fastの正体です。
3. 実装/解決策:なぜエラーになるのか、どう回避するのか
よくある間違いは、拡張forループ(for-each)の最中に、自分自身のコレクションに対して「削除」を行うことです。
回避策:
1. イテレータの remove() メソッドを使用する。
2. Java 8以降であれば、removeIf() メソッドを使用する。
3. Stream APIを使用して、新しいリストを生成する(非破壊的な操作)。
4. サンプルプログラム:安全な削除方法
以下のコードで、エラーが発生するケースと、正しく安全に削除を行うケースを確認してみましょう。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 【安全な削除方法】Iteratorのremoveメソッドを使う
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
if ("Banana".equals(fruit)) {
// イテレータ経由で削除すればmodCountが正しく更新される
iterator.remove();
}
}
System.out.println("削除後のリスト: " + list);
// 【よりモダンな方法】Java 8以降ならremoveIfがおすすめ
list.removeIf(f -> f.equals("Apple"));
System.out.println("removeIf後のリスト: " + list);
}
}
5. 応用・注意点:現場での実務アドバイス
実務において注意すべき点は以下の通りです。
・マルチスレッド環境での注意:
単一のスレッド内だけでなく、複数のスレッドで一つのコレクションを共有している場合もこの例外が発生します。その場合は、ConcurrentHashMapなどの「スレッドセーフなコレクション」を使用するか、適切に同期処理(synchronized)を行う必要があります。
・Sequenced Collections(Java 21〜):
最近導入されたSequenced Collectionsでも基本的なイテレータの仕様は変わりません。新しい機能を使っても、ループ中の構造変更には常に注意を払う必要があります。
・まとめ:
「ループ中にコレクションを直接変更しない」という原則さえ守れば、この例外は怖くありません。もし削除が必要な場合は、上記のように安全なメソッドを選択する癖をつけましょう。これがシニアエンジニアへの近道です!

コメント