Java – ConcurrentModificationExceptionエラーの原因や対処法を解説
ConcurrentModificationExceptionは、コレクション(例:ArrayListやHashMap)を反復処理中に構造が変更された場合にスローされる例外です。
主な原因は、イテレータを使用している間にコレクションを直接変更することです(例:addやremoveメソッドの呼び出し)。
対処法としては、1) イテレータのremove
メソッドを使用する、2) CopyOnWriteArrayList
などスレッドセーフなコレクションを使用する、3) 変更後に新しいイテレータを作成する、などがあります。
ConcurrentModificationExceptionとは
ConcurrentModificationException
は、Javaプログラミングにおいて、コレクション(リストやセットなど)を同時に変更しようとした際に発生する例外です。
このエラーは、特にイテレータを使用してコレクションを走査している最中に、別のスレッドや同じスレッド内でコレクションの内容を変更した場合に発生します。
この例外は、データの整合性を保つために重要であり、意図しない動作を防ぐ役割を果たします。
具体的には、以下のような状況で発生します。
- イテレータを使用している最中に、コレクションの要素を追加または削除する。
- 複数のスレッドが同じコレクションに対して同時に変更を行う。
このエラーを理解することで、より安全で効率的なプログラムを書くことが可能になります。
次に、具体的な例を見ていきましょう。
ConcurrentModificationExceptionが発生する具体例
ConcurrentModificationException
が発生する具体的なシナリオを理解するために、以下のサンプルコードを見てみましょう。
このコードでは、ArrayList
を使用して要素を走査しながら、同時に要素を削除しようとしています。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("ばなな");
fruits.add("みかん");
// イテレータを使用してリストを走査
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
// リストの要素を削除しようとする
if (fruit.equals("ばなな")) {
fruits.remove(fruit); // ここでConcurrentModificationExceptionが発生
}
}
}
}
このコードを実行すると、以下のようなエラーメッセージが表示されます。
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1006)
at App.main(App.java:10)
この例では、ArrayList
の要素をイテレータで走査している最中に、remove
メソッドを使って要素を削除しようとしています。
この操作は、イテレータがコレクションの構造を変更するため、ConcurrentModificationException
が発生します。
次のセクションでは、このエラーを回避する方法について説明します。
ConcurrentModificationExceptionの対処法
ConcurrentModificationException
を回避するためには、いくつかの方法があります。
以下に代表的な対処法を示します。
1. イテレータのremoveメソッドを使用する
イテレータには、要素を安全に削除するためのremove
メソッドがあります。
このメソッドを使用することで、コレクションの構造を変更することができ、例外を回避できます。
以下のサンプルコードを参照してください。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("ばなな");
fruits.add("みかん");
// イテレータを使用してリストを走査
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
// イテレータのremoveメソッドを使用して要素を削除
if (fruit.equals("ばなな")) {
iterator.remove(); // これでConcurrentModificationExceptionを回避
}
}
System.out.println("残りのフルーツ: " + fruits);
}
}
りんご
ばなな
みかん
残りのフルーツ: [りんご, みかん]
2. CopyOnWriteArrayListを使用する
スレッドセーフなコレクションを使用することで、ConcurrentModificationException
を回避することもできます。
CopyOnWriteArrayList
は、書き込み時に新しい配列を作成するため、読み取り中に変更があっても例外が発生しません。
以下のサンプルコードを見てみましょう。
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class App {
public static void main(String[] args) {
List<String> fruits = new CopyOnWriteArrayList<>();
fruits.add("りんご");
fruits.add("ばなな");
fruits.add("みかん");
// リストを走査しながら要素を削除
for (String fruit : fruits) {
System.out.println(fruit);
// 要素を削除
if (fruit.equals("ばなな")) {
fruits.remove(fruit); // これでもConcurrentModificationExceptionは発生しない
}
}
System.out.println("残りのフルーツ: " + fruits);
}
}
りんご
ばなな
みかん
残りのフルーツ: [りんご, みかん]
3. コレクションのコピーを作成する
コレクションのコピーを作成し、そのコピーを走査する方法もあります。
これにより、元のコレクションを変更しても例外が発生しません。
以下のサンプルコードを参照してください。
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("ばなな");
fruits.add("みかん");
// コレクションのコピーを作成
List<String> fruitsCopy = new ArrayList<>(fruits);
// コピーを走査しながら元のリストを変更
for (String fruit : fruitsCopy) {
System.out.println(fruit);
// 元のリストの要素を削除
if (fruit.equals("ばなな")) {
fruits.remove(fruit); // これでもConcurrentModificationExceptionは発生しない
}
}
System.out.println("残りのフルーツ: " + fruits);
}
}
りんご
ばなな
みかん
残りのフルーツ: [りんご, みかん]
これらの方法を使用することで、ConcurrentModificationException
を回避し、安全にコレクションを操作することができます。
次のセクションでは、エラーを防ぐためのベストプラクティスについて説明します。
ConcurrentModificationExceptionを防ぐためのベストプラクティス
ConcurrentModificationException
を防ぐためには、以下のベストプラクティスを実践することが重要です。
これにより、コレクションの操作を安全に行うことができます。
1. イテレータを使用する際はremoveメソッドを活用する
イテレータを使用してコレクションを走査する際は、要素を削除する場合に必ずイテレータのremove
メソッドを使用しましょう。
これにより、コレクションの構造を安全に変更できます。
2. スレッドセーフなコレクションを選択する
マルチスレッド環境でコレクションを使用する場合は、CopyOnWriteArrayList
やConcurrentHashMap
などのスレッドセーフなコレクションを選択することが推奨されます。
これにより、同時に複数のスレッドがコレクションを操作しても安全です。
3. コレクションのコピーを作成する
コレクションを変更する必要がある場合は、元のコレクションのコピーを作成し、そのコピーを走査する方法も有効です。
これにより、元のコレクションを変更しても例外が発生しません。
4. ループの外でコレクションを変更する
コレクションを走査している最中に変更を加えないようにし、ループの外で変更を行うことを心がけましょう。
これにより、ConcurrentModificationException
を回避できます。
5. コレクションのサイズを確認する
コレクションのサイズを確認し、変更が必要な場合は、サイズが変わらないことを確認してから操作を行うことも一つの方法です。
これにより、意図しない変更を防ぐことができます。
6. 適切なデータ構造を選択する
使用するデータ構造を適切に選択することも重要です。
例えば、頻繁に要素を追加・削除する場合は、LinkedList
を使用することが適しています。
これにより、パフォーマンスを向上させつつ、例外の発生を抑えることができます。
7. コードレビューを実施する
チームでの開発においては、コードレビューを実施することで、潜在的な問題を早期に発見し、ConcurrentModificationException
のリスクを低減できます。
これらのベストプラクティスを実践することで、ConcurrentModificationException
を防ぎ、より安全で効率的なJavaプログラムを作成することができます。
まとめ
この記事では、ConcurrentModificationException
の概要や発生する具体例、対処法、そしてこのエラーを防ぐためのベストプラクティスについて詳しく解説しました。
これらの知識を活用することで、コレクションを安全に操作し、プログラムの安定性を向上させることが可能です。
今後は、これらの対策を実践し、より堅牢なJavaプログラムを作成することを心がけてください。