クロージャの基本形
前回はJava SE 7で導入される予定のクロージャについて、その概要とプロトタイプ実装の使い方を紹介した。今回は、実際にクロージャを用いたプログラムを書いてみたい。
BGGA版の仕様では、クロージャを使うための基本となる演算子は「=>」となっている。クロージャを定義する基本的な形は、この演算子と{}を組み合わせて次にようになる。
{パラメータのリスト => ブロックステートメント}
クロージャを実行するにはinvoke()メソッドを呼び出す。たとえばリスト1は「1234」という数値を返すクロージャを定義し、invoke()を使って実行した例である。「{=>1234}」の部分がクロージャを表すブロックだ。
リスト1 FirstClosure.java - 最も基本的なクロージャの例
public class FirstClosure {
public static void main(String[] args) {
int answer = { => 1234 }.invoke();
System.out.println(answer);
}
}
パラメータを渡す場合にはリスト2のように「=>」の前にパラメータの型と変数名を宣言する。そしてinvoke()の呼び出しの部分で、通常のメソッドの場合と同様に実際に渡す値を指定する。
リスト2 クロージャにパラメータを渡す
int answer = { int x => x * x }.invoke(10);
System.out.println(answer);
2つ以上のパラメータを渡したい場合には、リスト3のようにコンマで続けて指定すればよい。
リスト3 2つ以上のパラメータはコンマで続けて指定
int answer = { int x, int y => x + y }.invoke(1357, 8642);
System.out.println(answer);
さて、プロトタイプのコンパイラでは、このクロージャはインタフェースのインスタンスとして実装されている。実際にコンパイルしてみると(コンパイル方法は第53回を参照)、javax.lang.function.Iやjavax.lang.function.IIIといったインタフェースが自動的に生成されるはずだ。これらはinvoke()というメソッドをひとつだけ持ったインタフェースとして定義されている(ただし実際に中身が定義されたインタフェースはjavax.lang.function.unrestrictedパッケージにある。プロトタイプであるため、細かな部分の扱い方はまだ決定されていない)。
たとえば前述のClosureWith2Param.javaをコンパイルした場合、javax/lang/functionディレクトリ(およびjavax/lang/function/unrestrictedディレクトリ)にIII.classというファイルが生成されるはずだ。ためしにunrestrictedの方のクラスをJadなどのツールを使って逆コンパイルしてみると、リスト4のような内容になっている。
リスト4 III.classを逆コンパイル
public interface III
{
public abstract int invoke(int i, int j) throws Throwable;
}
一方、ClosureWith2Param.classの方を逆コンパイルしてみるとリスト5のようになる。これを見ると、クロージャの部分はjavax.lang.function.IIIインタフェースをimplementsした_cls1というstaticクラスになっていることがわかる。当然、invoke()メソッドの実装がクロージャのinvoke()メソッドを呼び出した際に実行される内容だ。この_cls1のインスタンスがClosureWith2Paramのフィールドとして用意されて、それを介してinvoke()メソッドが実行されている。
リスト5 ClosureWith2Param.classを逆コンパイル
import java.io.PrintStream;
import javax.lang.function.III;
public class ClosureWith2Param {
public ClosureWith2Param() {}
public static void main(String args[]) {
static class _cls1 implements III {
public final int _2B_invoke(int j, int k) {
return j + k;
}
public final int invoke(int j, int k) {
return _2B_invoke(j, k);
}
}
int i = _2B_INSTANCE0.invoke(1357, 8642);
System.out.println(i);
}
public static final _cls1 _2B_INSTANCE0 = new _cls1();
}
関数型の利用
クロージャとあわせて関数型も利用できるようになる。関数型はクロージャに対する参照を代入するために使用する型で、クロージャと同様に=>と{}を組みわ合せで次のような形で記述する。
{ パラメータの型のリスト => 戻り値の型}
たとえば前述の3つのサンプル (リスト1、リスト2、リスト3) を、関数型を使って一度変数に代入してから実行するように書き換えるとリスト6のようになる。
リスト6
{ => int } number = { => 1234 };
System.out.println(number.invoke());
{ int => int } square = { int x => x * x };
System.out.println(square.invoke(10));
{ int, int => int } plus = { int x, int y => x + y };
System.out.println(plus.invoke(1357, 8642));
関数型はメソッドのパラメータや戻り値の型としても指定することができる。指定する方法は通常のクラスの場合と同様。たおえば文字列を1つ受け取り、戻り値のない関数型は{String => void}のように表すことができる。これをメソッドのパラメータとして用いた例がリスト7のsayHello()メソッドである。
リスト7 FunctionTypeAsParam.java - メソッドのパラメータに関数型を指定
public class FunctionTypeAsParam {
static void sayHello(String[] persons,
{String => void } block) {
for (String person: persons) {
block.invoke(person);
}
}
public static void main(String[] args) {
String[] persons = {"Taro", "Keiko"};
sayHello(persons,
{String p => System.out.println("Hello " + p + "."); });
sayHello(persons,
{String p => System.out.println("Good morning " + p + "."); });
}
}
これを実行するとsayHello()は渡された文字列配列のそれぞれの要素をクロージャのinvoke()に渡すという内容になっている。sayHello()を呼び出す際に渡すクロージャを変えれば、実行されるコードも変わる。この例の場合、1回目の呼び出しではパラメータの文字列に"Hello "を付けて標準出力に出力するという内容のクロージャを渡しており、2回目では"Good morning "を付けて出力するクロージャを渡している。これを実行するとプロンプト1のように出力される。
プロンプト1 FunctionTypeAsParam.javaの実行結果
> java FunctionTypeAsParam
Hello Taro.
Hello Keiko.
Good morning Taro.
Good morning Keiko.
リスト8はClosures for the Java Programming Languageに掲載されている関数型をメソッドの戻り値に指定した例である。この例では、makeCondition()メソッドが{ => boolean }という関数型を返すように宣言されており、実際には{ => Math.random() < 0.5 }というクロージャを返している。すなわち、makeCondition()メソッドの戻り値はランダムでtrueかfalseを返すクロージャとなる。
リスト8 FunctionTypeAsReturnType.java - メソッドの戻り値に関数型を指定
public class FunctionTypeAsReturnType {
static { => boolean } makeCondition() {
return { => Math.random() < 0.5 };
}
public static void main(String[] args) {
{ => boolean } condition = makeCondition();
for (int c=1; condition.invoke(); c++) {
System.out.println("trying " + c + " times...");
}
}
}
なんとなくクロージャを含んだJavaプログラムが一体どういうものになるのかわかっていただけただろうか。「=>」という演算子にはまだ強い違和感を覚えるが、普及すればジェネリクスのように自然に読めるものになっていくだろうと期待したい。