すがブロ

sugamasaoのhatenablogだよ

JDK5の機能(2)

前回→id:seiunsky:20060707:1152104056
今回は拡張for文について。
拡張for文……ようするにPerlC#のforeachと同じ感じで、配列などの要素を先頭から引っ張ってこれるってやつ。

int[] test = new int[10];
for (int i in test) {
    処理
}

構文としてはこのようになる。
で、この拡張for文というのは、とても便利に見えるのだけど、実際のところはどうなのか。実験してみた。
まず、ArrayListで試してみる。以下のコードはArrayListにInteger要素をaddして(int型だとプリミティブ型だからできない)、それらを取り出しながらIntegerの値の合計を計算している。
ついでに、for文の終了条件にxxx.size()と使うところをローカル変数を使ってsize()メソッドを呼び出さなくしたバージョンのテストケースも作ってみた。for文の動きの参考になれば幸い。

	private static void testArrayList() {
		long time;
		int sum;
		
		System.out.println("ArrayList(10000)で検証");

		// リストに値を格納する
		List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < 10000; i++) {
			list.add(new Integer(i));
		}

		// リストから値を読み出して合計する(拡張forループ)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (Integer i : list) {
				sum += i.intValue();
			}
		}

		System.out.println("拡張forループ : " + (System.currentTimeMillis() - time)
				+ "ミリ秒");

		// リストから値を読み出して合計する(通常forループ)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (int i = 0; i < list.size(); i++) {
				sum += list.get(i).intValue();
			}
		}

		System.out.println("通常forループ : " + (System.currentTimeMillis() - time)
				+ "ミリ秒");

		// リストから値を読み出して合計する(通常forループ:length変数使用)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (int i = 0, length = list.size(); i < length; i++) {
				sum += list.get(i).intValue();
			}
		}

		System.out.println("通常forループ(length使用) : "
				+ (System.currentTimeMillis() - time) + "ミリ秒");
	}

結果は……教えない。うそうそ。次に比較としてint配列でも同様のテストを行ってみる。
結果はそのあとに記述する。

	private static void testPrimitiveArray() {
		long time;
		int sum;
		
		System.out.println("int[10000]で検証");

		// 配列に値を格納する
		int[] array = new int[10000];
		for (int i = 0; i < 10000; i++)
			array[i] = i;

		// リストから値を読み出して合計する(拡張forループ)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (int i : array) {
				sum += i;
			}
		}

		System.out.println("拡張forループ : " + (System.currentTimeMillis() - time)
				+ "ミリ秒");

		// リストから値を読み出して合計する(通常forループ)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (int i = 0; i < array.length; i++) {
				sum += array[i];
			}
		}

		System.out.println("通常forループ : " + (System.currentTimeMillis() - time)
				+ "ミリ秒");

		// リストから値を読み出して合計する(通常forループ:length変数使用)
		sum = 0;
		time = System.currentTimeMillis();
		for (int l = 0; l < 10000; l++) {
			for (int i = 0, length = array.length; i < length; i++) {
				sum += array[i];
			}
		}

		System.out.println("通常forループ(length使用) : "
				+ (System.currentTimeMillis() - time) + "ミリ秒");
	}

では、お待ちかねの結果(とっても見難いと思うけど、見栄えの変え方がよくわからん)

テスト対象 拡張for文 通常for文 通常for文(変数使用)
ArrayList 3953ミリ秒 2625ミリ秒 1625ミリ秒
int配列 438ミリ秒 250ミリ秒 250ミリ秒

※参考:CPUはPen4 3.2Gで計測
……というわけで、恐ろしい結果がでてしまった。拡張for文は通常のfor文と比較すると遅いことがわかる。しかも誤差とか言える程度ではない。
ちなみに、この拡張for文はIteratorで実現することが多い*1ので、それも遅さの原因なのかもしれない。
また、参考にArrayListとint配列の拡張for文の部分をclassファイルから逆コンパイルしたソースを載せておく。
これがArrayListの方。

            for(Iterator iterator = list.iterator(); iterator.hasNext();)
            {
                Integer i = (Integer)iterator.next();
                sum += i.intValue();
            }

以下がint配列の場合

            int ai[] = array;
            int j = 0;
            for(int k = ai.length; j < k; j++)
            {
                int i = ai[j];
                sum += i;
            }

どちらも上記のテストメソッドで赤文字で記述した部分のfor文を載せている。少なくとも、コレクションなどのIteratorインターフェイスを実装しているものはIteratorを使っているようだ。そして、配列の場合は……よくわからんが頑張ってるようだ。
で、もう一つ。
各テストメソッドの最後のfor文について。
検証結果から、配列(lengthフィールドを持っているもの)に関してはあえてもう一つ変数を使用する必要性はないように思う*2
ただし、ArrayListのように毎回メソッド((size()))を呼ばないと長さが分からないものに関してははっきりと効果があると言えるだろう。
たまに、妄信的にfor文の終了条件の値は変数を一つ使わなきゃイカン!と言われる場所もあるので覚えておいてそんはないだろう。

*1:配列の場合ちょっと違う

*2:数回テストを行った結果、多少早くなったり遅くなったりするけど……