Java : ラムダ式
ラムダ式
Javaコミュニティでは関数オブジェクト(ファンクタ)の有用性について度々議論されてきた.
Java1.0で採用されたAbstract Window Toolkit(AWT)では, ウィンドウクラスを拡張し, クライアントは必要に応じてメソッドをオーバーライドする必要があった.
しかし, この方式は扱いづらくJava1.1では"リスナー"と呼ばれるインタフェースが導入された.
当時のSunはリスナーを実装するクラスの記述性を向上すべくインナークラスを導入した.
インナークラスはUIイベントのリスナーを登録する際に便利なものであるが, その構文やセマンティクスは不格好であり, 関数型プログラミングのようにコードブロックを受け渡しすることはJavaの不得手なところであった.
Java8では前述の課題を解決すべくラムダ式を導入した. ラムダ式は別名"クロージャ"とも呼ばれるが厳密には異なる.
ラムダ式はメソッド(コードブロック)の記述を簡素化するための手法である.
Java7までのRunnable定義(匿名クラス)をラムダ式に置き換えたのが次の例である.
// Java7 non-Lambda
Runnable r = new Runnable() {
public void run() {
System.out.println("Howdy, world!");
}
};
r.run();
// Java8 Lambda
Runnable r2 = () -> System.out.println("Howdy, world!");
r2.run();
どちらも同じ実行結果となるが, ラムダ式の方がより簡素に記述できることがわかる.
ただし, 内部的には匿名クラスのケースとラムダ式のケースとで異なっている.
NOTE
匿名クラスはコンパイラによってエンクロージングクラス名 + $連番 + .class (例: Hoge$1.class)
の命名規則でクラスファイルが作られる.
ラムダ式はエンクロージングクラスのクラスファイル内部に合成メソッドとして作成され, メソッド名はlambda$ + エンクロージングメソッド名 + $連番 (例: lambda$main$1)
の命名規則となる.
ラムダ式ではコンパイル時ではなく実行時にクラスファイルが生成される.ラムダ式はJava7で導入されたinvokedynamic命令の実行として実装されている.
匿名クラス扱いとせずinvokedynamic命令の実行とすることでクラスファイルの増加を抑え, クラスローディングにかかるオーバーヘッドを抑える効果が期待できる.
関数型インターフェース
Runnable, Callable, Comparatorのように, 実装されるメソッドが1つだけ存在するインタフェースを関数型インタフェースと呼ぶ.
Java8では@FunctionalInterface
アノテーションが追加され, 関数型インタフェースであることをこのアノテーションで明示することができる.
このアノテーションは単にラムダ式で使用されることを明示するだけのものであり, コンパイラでは参照されない.
開発者は, 実装されるメソッドが1つだけ存在する制約さえ守れば独自の関数インタフェースをいつでも定義できる.
@FunctionalInterface
interface Hoge {
public String foo();
}
レキシカルスコープ
ラムダ式にはレキシカルスコープが適用される.
ラムダ式を定義した周囲のコンテキストがそのまま引き継がれる.
public class Main {
@FunctionalInterface
interface Lambda {
public void print();
}
public static void main(String... args) {
new Main().execute();
}
private void execute() {
new Hoge().foo(() -> System.out.println(this));
}
}
class Hoge {
public void foo(Main.Lambda lambda) {
lambda.print();
}
}
// 出力結果: Main@5ca881b5
// lambda.print()で得られるthisはHogeではなくMainを指す.
また, ラムダ式はエンクロージングクラスを生成しないため, 匿名クラスをラムダ式に単純置換すると異なる挙動になるケースがある.
下記例では匿名クラスの指すthis
が匿名クラス自身(Main$1)を指すのに対して, ラムダ式でのthis
がエンクロージングクラスを指していることがわかる.
// 出力結果: Main$1@15db9742
public class Main {
public static void main(String... args) {
new Main().execute();
}
public void execute() {
Runnable r = new Runnable() {
public void run() {
System.out.println(this);
}
};
r.run();
}
}
// 出力結果: Main@15db9742
public class Main {
public static void main(String... args) {
new Main().execute();
}
public void execute() {
Runnable r = ()-> System.out.println(this);
r.run();
}
}