ConcurrentModificationException の原因と直し方
ループ中のリスト変更で発生します。removeIf、Iterator、逆順ループの3つの解法を比較します。
エラーメッセージの読み方
Exception in thread "main" java.util.ConcurrentModificationException
main- スレッド名 — mainなら起動直後の処理で発生しています
java.util- パッケージ — java.lang なら標準の基本例外です
ConcurrentModificationException- 例外クラス — 何が起きたか。ここを検索するのが最短です
このエラーが出る典型パターン
パターン1
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Main { 5 public static void main(String[] args) { 6 List<String> list = new ArrayList<>(List.of("a", "b", "c", "d")); 7 for (String s : list) if (s.equals("b")) list.remove(s); ^ 8 System.out.println(list); 9 } 10 }
Exception in thread "main" java.util.ConcurrentModificationExceptionexited with code 1
拡張forで回している最中にリストを変更すると例外になります。removeIfかIteratorを使います。
直し方: for (String s : list) if (s.equals("b")) list.remove(s); を list.removeIf(s -> s.equals("b")); にします。
広告
広告スロット(未設定)
パターン2
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Main { 5 public static void main(String[] args) { 6 List<Integer> list = new ArrayList<>(List.of(1, 2, 3)); 7 for (int i = list.size() ; i >= 0; i--) { ^ 8 if (list.get(i) == 2) list.remove(i); 9 } 10 System.out.println(list); 11 } 12 }
Exception in thread "main" java.lang.IndexOutOfBoundsExceptionexited with code 1
添字で削除するときは後ろから回します。前から削ると以降の要素が繰り上がって位置がずれます。
直し方: list.size() を list.size() - 1 にします。
パターン3
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class Main { 5 public static void main(String[] args) { 6 List<String> list = List.of("a") ; ^ 7 list.add("x"); 8 System.out.println(list); 9 } 10 }
Exception in thread "main" java.lang.UnsupportedOperationExceptionexited with code 1
List.ofは変更不可のリストを返します。追加や削除をするならArrayListに包み直します。
直し方: List.of("a") を new ArrayList<>(List.of("a")) にします。
よくある誤解
例外が出ないこともあります。最後から2番目の要素を削除すると検出をすり抜けるため、たまたま動いてしまいます。
まとめ
ConcurrentModificationExceptionは中級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。