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>
イテレータを使う場合の手続きは次の通りです。
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では、他にも要素の挿入/削除/置換などのメソッドがサポートされています。