#はじめに
- この記事は駆け出しエンジニアが書いています。間違いがありましたら遠慮なくご指摘いただけると幸いです。
- この記事はJavaのArrayListにおけるConcurrentModificationExceptionについて超ざっくりと解説します
#この記事の対象者について
- Java Silver勉強中で、どういう場合に発生するのか確認したい方
- Javaでシングルスレッドのコードを書いていたらConcurrentModificationExceptionが出たのでとりあえず解決したい方。
*詳しい解説はすべて省いて、初心者の方でも分かりやすいように書いています。
#ConcurrentModificationExceptionについて
まだまだ駆け出しの筆者であるが、Java Se8 Silverの問題集をやっているとこんな問題が出た
*例
//配列を宣言し「ABCDE」を要素として配列に格納
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
//拡張for文でリストの中身を一つづつ取り出し、
//「B」であればリストの中身の「B」を削除
//それ以外はコンソールに表示させる
for (String string : list) {
if(string.equals("B")) {
list.remove(string);
} else {
System.out.println(string);
}
}
これを実行すると例外が発生する
実行結果
A
Exception in thread "main" java.util.ConcurrentModificationException
Aだけ表示されていることからremoveメソッドで例外が発生しているのが分かると思う。
##なぜ発生するのか
結論から言うと、拡張for文で『要素の取り出し中にremoveメソッドを実行したから』となる。
これはマルチスレッド環境下で片方が要素を取り出している最中にもう片方が同じリストから要素を削除していまうことがないようにするために投げられるの例外なのだが、今回のようにシングルスレッド環境下でも発生する。
初心者の方は遭遇する機会がほとんどないため、理解するに見合うだけのリターンが少ない。よく分からなければ、そういう例外があるんだと漠然と思っていただければ十分だと思う。
##【Java Silver対策】どのタイミングなら発生しないのか
これも結論から言うと「listの要素の最後から2番目をremoveする場合は発生しない」となる
先述の問題に近い例題がある
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
for (String string : list) {
if(string.equals("D")) {
list.remove(string);
} else {
System.out.println(string);
}
}
拡張for文の中のequalsメソッドの中身を"D"に変えただけのコード。この実行結果はというと
実行結果
A
B
C
このように例外は発生しない。(なぜ"E"が表示されないかは別の問題であるためこの記事では扱わない)
このようにlistの要素である「ABCDE」の中の最後から2番目である"D"をremoveする場合は例外は発生しない。
逆に言えば"A""B""C""E"は全て例外が発生してしまう。
これもそういうものだと覚えてしまおう。
#ConcurrentModificationExceptionが発生しないようにするためには
実際にコードを書いているときにはあまり見ることはないだろうが、対応策として何例か考えたのでここに記述していく
通常は対応策1で十分だと思うが、どうしても要素を取り出しながら削除したいという方は「対応策3」「対応策4」を参考していただきたい。
##対応策1-先に削除しておく-
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.remove("B");
for (String string : list) {
System.out.println(string);
}
実行結果
A
C
D
E
とても単純だが拡張for文の前で要素の削除を行う。
どうしても要素の中身を取り出しながら削除したい、という人以外はこれで大丈夫だと思う
*以下の対応策からListの要素の追加まで省略
##対応策2 -通常のfor文を使う(非推奨)-
for (int i = 0; i < list.size(); i++) {
if(list.get(i).equals("B")) {
list.remove("B");
} else {
System.out.println(list.get(i));
}
}
実行結果
A
D
E
繰り返し処理の中で要素を削除したいという人は通常のfor文であれば例外は発生しない。
しかし、実行結果から分かる通り、要素の順番がずれるために実行結果は意図しないものになりがち。オススメはしない。
##対応策3 Iterator(イテレータ)を使う
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
if(string.equals("B")) {
iterator.remove();
} else {
System.out.println(string);
}
}
実行結果
A
C
D
E
イテレータの詳細も省くがイテレータには「要素を取り出しながら削除される」ことを想定されているため、イテレータを使うことで例外を回避できる。
どうしても要素を取り出しながら削除したい人はこれで解決すると思う。
##対応策4 -removeIfメソッドを使う
list.removeIf((String string) ->{
if(string.equals("B")) {
return true;
} else {
System.out.println(string);
return false;
}
});
実行結果
A
C
D
E
先述した通り、要素を取り出しながら削除する場合にはイテレータを使うしかないが、removeIfメソッドもイテレータを取り出してくれる。
ラムダ式で条件を指定し、trueであれば要素を削除してくれるメソッドだ。
コード内でイテレータを呼び出すよりも幾分かスッキリしたコードになるためこちらのほうが使いやすいかもしれないが、ラムダ式(->)は初心者には分かりづらいため、使用するときはコメントを書いておくといいかもしれない。
備考ではあるが、Java Silverを受ける方はremoveIfメソッドは試験範囲のため、覚えておいても損はない。
#最後に
ConcurrentModificationExceptionについて説明していきましたが、初心者の方はあまり見ることのないエラーのため、試験や、実際コードを書いている最中に見かけると戸惑ったかもしれません。
筆者もこのエラーについて調べた時に日本語で正しい解説をされているものが見つからなかったため、かなり戸惑いました。
ただ、あまり見かけることがないゆえに、中身を理解するよりもこの記事で「そういうものだ」と暗記してしまうのが楽かなと思います。
この記事が皆様のお役に立てたのなら幸いです。