読者です 読者をやめる 読者になる 読者になる

A little bit of everything

情報系大学院生の備忘録

【Java】for文でリストを回して要素を削除

for文でリストを回して要素を削除しようとして初歩的なミスをしてしまった…。注意が必要。

こんなコードがあったとする。

import java.util.ArrayList;

public class Sample1 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(4);
        list.add(6);
        list.add(8);
        list.add(9);

        for(int i=0; i<list.size(); i++){
            if(list.get(i) % 2 == 0) list.remove(i);
        }
        for(int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }
    }
}


このプログラムは次のような動作をするように見える

  1. リストに1,2,4,6,8,9という数を追加
  2. for文を回して全要素を見ていき、要素が偶数である数をリストから削除
  3. リストに残った数を全て表示


だけど実行結果はこうなる。

1
4
8
9

このコードの問題点は、 list.remove(i) と実行した瞬間に、要素が削除されるので、i番目以降の要素は、i番目が削除されたことにより、1つずつ前に詰める。 しかしfor文はi++, i++, … と、リストの中身なんか関係なく、要素が1つずつ前に詰めたことなんかお構いなしで次のインデックスを見ていく。 これによって、「シカト」される要素が出てきてしまう。このコードの場合は、4と8が「シカト」されてしまったということ。

ちなみに、、、
さっきのコードはfor文で i++,i++,...とやって要素を削除しているけど、拡張for文を使って "書こうと思えば" こんな風にも書ける。

import java.util.ArrayList;

public class Sample2 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(4);
        list.add(6);
        list.add(8);
        list.add(9);

        int j = 0;
        for (Integer i: list) {
             if(i % 2 == 0) list.remove(j);
             j++;
        }
        for(int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }
    }
}

最初のコードと同じ動きをしそうだけど、これを実行すると java.util.ConcurrentModificationException で怒られる。これは、繰り返しの中で Collection が変更されたときに投げられる例外らしい。

スマートな書き方

Iterator を使う!

import java.util.ArrayList;
import java.util.Iterator;

public class Sample3 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(4);
        list.add(6);
        list.add(8);
        list.add(9);

        Iterator<Integer> it = list.iterator();
        while(it.hasNext()){
            int i = it.next();
            if(i % 2 == 0) it.remove();
        }
        for(int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }
    }
}

こうやって書くのがいいらしい。

リファレンスにも書いてある。

Iterator (Java Platform SE 7 )

Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.