JSR-201 のはじめは拡張 for 文です。
普通に for 文使っていると、別に拡張なんかしなくてもいいと思うんですが...
何が拡張したかというと、要素がある限りループをするという動作が加わりました。これは Basic の for ... each 文、C# の foreach 文に相当するものです。
この拡張 for 文を使えば、今まで Iterator インタフェースを使っていたところが劇的に簡単に書けるようになります。
Iterator インタフェースを使うのは結構めんどくさいものです。例えば、次の例を見てください。単に List の要素を出力しているだけです。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorSample1 { public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 10; i++) { list.add(new Integer(i)); } Iterator it = list.iterator(); while (it.hasNext()) { Integer tmp = (Integer)it.next(); System.out.println(tmp); } } }
幸いにも Generics が使えるようになったので、少しだけ簡単になりました。それでもキャストが取れただけです。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorSample2 { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10; i++) { list.add(new Integer(i)); } Iterator<Integer> it = list.iterator(); while (it.hasNext()) { Integer i = it.next(); System.out.println(i); } } }
もうちょっと簡単にしたいと思いません ?
そこで登場するのが拡張 for 文です。
拡張 for 文は次のように記述します。
for (FormalParameter : Expression) Statement;
よく分かりませんね ^^;; それでは、先ほどの例を拡張 for 文を使って書いてみましょう。まずは Generics を使わないようにしたものです。
import java.util.ArrayList;
import java.util.List;
public class IteratorSample3 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(new Integer(i));
}
for (Object o : list) {
System.out.println((Integer)o);
}
}
}
赤い字の部分が拡張 for 文です。なんかすっごく簡単になった気がしません。
先ほどの記述法に照らし合わせて見ましょう。Expression には List オブジェクトが対応しています。ここにはコレクションや配列を記述できます。
FormalParameter はコレクションの要素を扱うための変数を記述します。コレクションの場合は要素は Object クラスのオブジェクトとして保持されるので、Object クラスの変数とします。
Statement にはなんでも書けます。FormalParameter に書いた変数も使うことができます。
ループは Expression の部分のコレクション・配列の要素を頭から取り出します。順々に要素が取り出され、最後までいったらおしまいです。
Generics を使うと、Object クラスにしていた変数をパラメータかすることができます。つまり、こういうこと。
import java.util.ArrayList;
import java.util.List;
public class IteratorSample4 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(new Integer(i));
}
for (Integer i : list) {
System.out.println(i);
}
}
}
どんどん簡単になっちゃいます ^^;;
実際に拡張 for 文で使えるのはどんなクラスなんでしょうか。こんなことをやってみました。
import java.util.*; public class ExtendedForTest1 { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (String s : list) { System.out.println(s); } Map<String, String> map = new HashMap<String, String>(); for (String s : map.values()) { System.out.println(s); } Set<String> set = new HashSet<String>(); for (String s : set) { System.out.println(s); } Vector<String> vector = new Vector<String>(); for (String s : vector) { System.out.println(s); } Iterator<String> it = list.iterator(); for (String s : it) { System.out.println(s); } String[] array = new String[5]; for (String s : array) { System.out.println(s); } } }
これをコンパイルすると、
C:\JSR201\examples>javac ExtendedForTest1.java
ExtendedForTest1.java:26: foreach not applicable to expression type
for (String s : it) {
^
エラー 1 個
C:\JSR201\examples>
Iterator を簡単に扱うための拡張 for 文なので、Iterator は使えないのでしょう ^^;;
しかし、それ以外のものはほとんど使えそうです。
仕様書を読んでみると、java.lang.Iterable インタフェースを継承しているものが拡張 for 文で使用できると記述されています。はて、Iterable インタフェースとはなんぞや?
Iterable インタフェースは Tiger で導入された新しいインタフェースです。
中身はこんな感じです (JSR-14 のリファレンスインプリメンテーションにソースがあります)。
/* * @(#)Iterable.java 1.2 03/09/09 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.lang; import java.util.Iterator; /** Implementing this interface allows an object to be the target of * the "foreach" statement. */ public interface Iterable<T> { /** * Returns an iterator over a set of elements of type T. * * @return an Iterator. */ Iterator<T> iterator(); }
iterator というメソッドが 1 つ定義されているだけです。戻り値は Generics の Early Access では SimpleIterator というインタフェースだったのですが、Iterator インタフェースに変更されたようです。
さて、さきほど Iterable インタフェースをインプリメントしたクラスで拡張 for 文を使用できると書きましたが、実際にはどうなっているのでしょう。例えば、java.util.Collection インタフェースは次のように書き換えられています。
package java.util; public interface Collection<E> extends Iterable<E> { ...
これで拡張 for 文が使えるようになっていたわけです。
Iterable インタフェースは拡張 for 文だけに使用されるので、実際にユーザがこれを使用してプログラムを書くことはほとんどないと思います。ですから、あまり気にすることなく拡張 for 文を使いましょう。
とはいうもののやっぱり気になる、裏側が。
というわけで、今回も逆コンパイラの Jad を使って調べてみました。
逆コンパイルしたのは IteratorSample3 です。
Jad の結果は次のようになりました。
import java.io.PrintStream; import java.util.*; public class IteratorSample3 { public IteratorSample3() { } public static void main(String args[]) { ArrayList arraylist = new ArrayList(); for(int i = 0; i < 10; i++) arraylist.add(new Integer(i)); Object obj; for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println((Integer)obj)) obj = iterator.next(); } }
拡張 for 文が排除したかった Iterator が復活しているわけですね。コンパイラがこの面倒くさい部分を自動的に補ってくれるわけです。
これは List を使った例でしたが、配列ではどうでしょうか。IteratorSample3 クラスを配列を使ったものに書きかえたのが IteratorSample5 クラスです。
これも同様に Jad で逆コンパイルしてみました。
import java.io.PrintStream; public class IteratorSample5 { public IteratorSample5() { } public static void main(String args[]) { int ai[] = new int[10]; for(int i = 0; i < 10; i++) ai[i] = i; int ai1[] = ai; int j = ai1.length; for(int k = 0; k < j; k++) { int l = ai1[k]; System.out.println(l); } } }
配列の場合はごくごく普通のインデックスを用いたアクセスになっていました。相手を見て変換の方法を変えているようです。
えらいなぁ、コンパイラ。
本質的な問題ですが、Iterator インタフェースって使いますか?
私はほとんど使いません ^^;; というのも、私がコレクションで一番多く使うのは ArrayList クラスで、RandomAccess インタフェースをインプリメントしています。RandomAccess インタフェースをインプリメントしているクラスは Iterator インタフェースを使うより、普通に get した方が高速なのです。
ですから、もし LinkedList クラスを使うのであれば Iterator インタフェースを使用します。
そんな私には拡張 for 文のご利益もないのでしょうか。
心配するより測りましょう。
ArrayList クラスを使って、普通に get するのと拡張 for 文を使うのを比べてみました。比較に使ったのは次のプログラム。
import java.util.ArrayList; import java.util.List; public class ComparisonTest1 { public static void main(String[] args) { Listlist = new ArrayList (); for (int i = 0; i < 30000; i++) { list.add(Integer.toString(i)); } StringBuffer buffer = new StringBuffer(); long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { for (String s : list) { buffer.append(s); } } long end = System.currentTimeMillis(); System.out.println("拡張 for 文: " + (end - start)); buffer = new StringBuffer(); start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { for (int j = 0; j < list.size(); j++) { buffer.append(list.get(j)); } } end = System.currentTimeMillis(); System.out.println("for 文: " + (end - start)); } }
さて、実行です。筆者のマシンは Athron 1.2 MHz, Windows XP SP1 でメモリは 512MB です。使ったのは J2SE, SDK 1.5 alpha です。
C:\JSR201\examples>java ComparisonTest1
拡張 for 文: 3495
for 文: 2894
C:\JSR201\examples>
まぁ、予想通りでした。文法は簡単になりましたが、結局は内部でイテレータを使っているのですから、この結果は納得できます。やはり、RandomAccess インタフェースを使用しているものは普通に get したほうがよさそうです。
たぶん同じような結果が出ると思いますが、配列でも試してみました。
import java.util.ArrayList; import java.util.List; public class ComparisonTest2 { public static void main(String[] args) { String[] array = new String[30000]; for (int i = 0; i < 30000; i++) { array[i] = Integer.toString(i); } StringBuffer buffer = new StringBuffer(); long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { for (String s : array) { buffer.append(s); } } long end = System.currentTimeMillis(); System.out.println("拡張 for 文: " + (end - start)); buffer = new StringBuffer(); start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { for (int j = 0; j < array.length; j++) { buffer.append(array[j]); } } end = System.currentTimeMillis(); System.out.println("for 文: " + (end - start)); } }
C:\JSR201\examples>java ComparisonTest2
拡張 for 文: 3214
for 文: 2564
C:\JSR201\examples>
まぁ、こんなものでしょう。配列も普通に get したほうがよさそうです。
ただ、それほどパフォーマンスを気にしなくていい場面であれば、拡張 for 文を使ったほうが読みやすいと思います。
JavaOne 2003 で発表された Java のコンセプト Ease of Develpment を実現するための 1 つとしての拡張 for 文はいかがでしたか。Java が Java でなくなってきてしまう感じもありますが、他の言語のいいところがあればどんどん取り入れていくのも 1 つの方法だと思います。
例えば、VB からの転向組が Java になじみやすくなるのは確かかもしれません。
でも、今まで Java を使ってきた人はそれほど使うかなぁ....
今回使用したサンプルはここからダウンロードできます。
参考
(Oct. 2003)
(改訂 Jan. 2004)
(改訂 Feb. 2004)