へっぽこITエンジニア@名古屋のブログ

Follow me on GitHub

Listの参照中の編集

Listはスレッドセーフではないので、マルチスレッドでなくても参照中に編集するとjava.util.ConcurrentModificationExceptionが発生することがあります。

しかし、以外に発生する場合としない場合があるので、記載します。

拡張for文を利用する場合です。

        List<String> list = new ArrayList<>();
        
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        
        for(String str : list){
            if("c".equals(str)) {
                list.remove("c");
            }else {
                System.out.println(str);
            }
        }

上記の場合は

a
b

と正常終了します。

cが削除されてずれるため、最後に参照されないのでjava.util.ConcurrentModificationExceptionは発生しません。

次にListに1つ要素を足した場合です。

        List<String> list = new ArrayList<>();
        
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        
        for(String str : list){
            if("c".equals(str)) {
                list.remove("c");
            }else {
                System.out.println(str);
            }
        }

この場合はeを参照する際にjava.util.ConcurrentModificationExceptionが発生します。

結果はこんな感じ。

a
b
Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
    ・・・・

cが削除されますが、ずれて最後のeが参照されるためNGなのです。

次に普通にfor文を利用した場合です。

        List<String> list = new ArrayList<>();
        
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        
        for(int i = 0; i < list.size(); i++){
            String str = list.get(i);
            
            if("c".equals(str)) {
                list.remove("c");
            }else {
                System.out.println(str);
            }
        }

こちらは正常に動き結果は以下の通りになります。

a
b
e

参照と編集が同時には行われていないからですかね。

次にforEachを利用した場合です。

        List<String> list = new ArrayList<>();
        
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        list.forEach(str ->{
            if("c".equals(str)) {
                list.remove("c");
            }else {
                System.out.println(str);
            }
        });

こちらはcを削除した瞬間にjava.util.ConcurrentModificationExceptionが発生します。

結果はこんな感じ。

a
b
Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1542)
    ・・・

色々なパターンがあるので、for文を回してlist自体を削除するのが、あまりよくないのかもしれませんね。 新しいListを作ってそちらに格納するか、 list.romve("c");や条件が複雑ならlist.removeIf(s -> "c".equals(s));などを利用するのがよいですかね。

作成日:2021-08-13  更新日:2021-08-13