コレクション・クラスに対する拡張for文

Revised: May/6th/2008;

J2SE 5.0以上ではfor-eachループ(拡張for文)を使うことで、リストの各要素へのアクセスを簡単に書けます。

簡単な例

コレクションオブジェクトにおけるfor-each文は、次のようになります。

import java.util.ArrayList;

class TestForIn2 {
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < Integer.valueOf(args[0]); i++) {
			list.add(i);
		}
		// listオブジェクトの各要素を変数oへ代入して、
		// 全要素について終了するまで繰り返し
		for (Integer o : list) {
			if (o % 3000 == 0) {
				System.out.println(o.toString());
			}
		}
	}
}
D:\java>javac TestForIn2.java

D:\java>java TestForIn2 40000
0
3000
6000
9000
12000
15000
18000
21000
24000
27000
30000
33000
36000
39000

D:\java>
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

class TestForIn3 {
	public static void main(String[] args) {
		String[] strs = {"mono","di","tri","tetra","penta","hexa","hepta",
			"octa","nona","deca(10)", "undeca(11)","dodeca(12)"};
		List<String> strList = Arrays.asList(strs);
		ArrayList<String> list = new ArrayList<String>(strList);

		// listオブジェクトの各要素を変数sへ代入して、
		// 全要素について終了するまで繰り返し
		for (String s : list) {
			System.out.print(s + "; ");
		}
	}
}
D:\java>javac TestForIn3.java

D:\java>java TestForIn3
mono; di; tri; tetra; penta; hexa; hepta; octa; nona; deca(10); undeca(11); dodeca(12);
D:\java>

動作特性

LinkedListはインデックスが前後の要素をポイントしているので、順次アクセスに対して最適化されています。ArrayListはランダムアクセスに最適化されており、効果的に反復を実行する仕組みを持っていません。その都度ランダムアクセスするため、LinikiedListに比べると若干遅くなります。

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

class ListIterationDemo {
	void elapsed(List<Integer> list) {
		long start = System.currentTimeMillis();
		int i = 0;
		for (Object o : list) {
			i++;
			o.hashCode();	// oに関する処理。何でも良い
			if (i % 100000 == 0) {
				long end = System.currentTimeMillis();
				System.out.println(i + ": " + (end - start));
			}
		}
	}
}
class ArrayListIterationDemo {
	public static void main(String[] args) {
		int MAX = Integer.valueOf(args[0]);
		
		Integer[] intarray = new Integer[MAX];
		for (int i = 0; i < MAX; i++) {
			intarray[i] = i;
		}

		List<Integer> list = Arrays.asList(intarray);
		ListIterationDemo demo = new ListIterationDemo();
		
		if (args[1].equals("ArrayList")) {
			list = new ArrayList<Integer>(list);
			System.out.println("ArrayList");
			demo.elapsed(list);
		} else if (args[1].equals("LinkedList")) {
			list = new LinkedList<Integer>(list);
			System.out.println("LinkedList");
			demo.elapsed(list);
		}
	}
}
D:\java>javac ArrayListIterationDemo.java

D:\java>java ArrayListIterationDemo 1000000 ArrayList
ArrayList
100000: 16
200000: 32
300000: 47
400000: 63
500000: 79
600000: 94
700000: 94
800000: 110
900000: 126
1000000: 126

D:\java>java ArrayListIterationDemo 1000000 LinkedList
LinkedList
100000: 16
200000: 16
300000: 16
400000: 32
500000: 32
600000: 32
700000: 32
800000: 47
900000: 47
1000000: 47

D:\java>

ArrayListよりもLinkedListの方が、順序アクセスでは高速であることが分かります。

イタレーション

拡張for文を用いれば、イテレータを利用する機会は殆どないでしょうが、ここではイテレータを利用した場合のコレクションオブジェクトの処理方法も説明しておきます。

次の例は、上記のfor-each文をイテレータを用いて書き直したものです。

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;

class ListIterationDemo2 {
	<A extends Number> void elapsed(List<A> list) {
		long start = System.currentTimeMillis();
		int i = 0;
		for (Iterator<A> itr = list.iterator(); itr.hasNext();) {
			i++;
			itr.next().hashCode();	// oに関する処理。何でも良い
			if (i % 100000 == 0) {
				long end = System.currentTimeMillis();
				System.out.println(i + ": " + (end - start));
			}
		}
	}
}
class ArrayListIterationDemo2 {
	public static void main(String[] args) {
		int MAX = Integer.valueOf(args[0]);
		
		Integer[] intarray = new Integer[MAX];
		for (int i = 0; i < MAX; i++) {
			intarray[i] = i;
		}

		List<Integer> list = Arrays.asList(intarray);
		ListIterationDemo2 demo = new ListIterationDemo2();
		
		if (args[1].equals("ArrayList")) {
			list = new ArrayList>Integer>(list);
			System.out.println("ArrayList");
			demo.elapsed(list);
		} else if (args[1].equals("LinkedList")) {
			list = new LinkedList>Integer>(list);
			System.out.println("LinkedList");
			demo.elapsed(list);
		}
	}
}
D:\java>javac ArrayListIterationDemo2.java

D:\java>java ArrayListIterationDemo2 1000000 ArrayList
ArrayList
100000: 32
200000: 47
300000: 63
400000: 79
500000: 94
600000: 110
700000: 126
800000: 141
900000: 157
1000000: 173

D:\java>java ArrayListIterationDemo2 1000000 LinkedList
LinkedList
100000: 0
200000: 16
300000: 16
400000: 31
500000: 31
600000: 47
700000: 47
800000: 63
900000: 63
1000000: 78

D:\java>

イテレータを使う場合の手続きは次の通りです。

  1. オブジェクトからIterator型オブジェクトを生成
  2. Iterator型オブジェクトのメソッドhasNext()で現在位置の次の要素が存在するか評価
  3. Iterator型オブジェクトのメソッドnext()で次の要素を取り出して、次の要素へ移動する
public static void printAll(Collection<?> collection) {
	Iterator<?> it = collection.iterator();
	while(it.hasNext()) {                 // 次の要素が存在する限りブロック内を繰り返し実行
		System.out.println(it.next().toString());    // 次の要素を取得してその次の要素へ進む
	}
}

これは、次のように書くこともできます。

for (Iterator<?> it = collection.iterator(); it.hasNext();) {
	System.out.println(it.next().toString());          // 全ての要素を順番にプリント
}

コレクション・フレームワークでは、要素を順番にアクセスするために、対象のコレクション・オブジェクトから、インタフェースIterator型のオブジェクトを取得して、メソッドhasNext(), next()を使います。

Vectorでは、Iteratorのほかに、古いAPIであるインタフェースEnumerationも使えます。Enumerationで提供される機能はIteratorでも提供されています。Iteratorを使うことで、オブジェクトを他のコレクション・クラス型へ変更することができるようになります。

// Vector型オブジェクトvecのEnumerationの利用
for (Enumeration e = vec.elements(); e.hasMoreElements();) {
	System.out.println(e.nextElement().toString());    // 全ての要素を順番にプリント
}

動作特性としてまとめると、LinkedListでは、要素のアクセスに、前後の要素へのリンクを利用して、先頭から順番にアクセスしていくので、要素を索引番号で直接アクセスできるArrayListに比べて、索引番号による要素の特定(ランダム・アクセス)が著しく遅くなります。その一方で、途中の要素の挿入/削除時には、後続要素のコピーが必要になるArrayListに比べて、前後の要素のポインタの付け替えだけで済むので高速になります。

IteratorのほかにListIteratorを使うこともできます。Iteratorでは、次の要素への移動しかサポートしていませんが、ListIteratorでは逆方向へ要素をたどることができます。

// indexから逆方向に全ての要素をプリント
public static void printReverse(LinkedList list, int index) {
	for (ListIterator lit = list.listIterator(index); lit.hasPrevious();) {
		System.out.println(lit.previous());
	}
}

ListIteratorでは、他にも要素の挿入/削除/置換などのメソッドがサポートされています。



Copyright © 2003-2008 SUGAI, Manabu. All Rights Reserved.