Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Java SE 8に導入されるのは、ほんとにクロージャ!?

Javaプログラマが悩むラムダとクロージャと匿名関数と - Fight the Future じゅくのblog
http://d.hatena.ne.jp/jyukutyo/20111117/1321587651
の続き。

セットアップ

ラムダ式の使えるJava SE 8のアーリービルドは、ここからダウンロードできます。


Java Platform, Standard Edition 8 Developer Preview Releases ― Java.net
http://jdk8.java.net/lambda/


PATHを通せばセットアップ完了。


Java SE 8とか言いながら、ラムダ式が使える版は、
OpenJDK 7の最新版にラムダプロジェクトを足したものだそうです。
(ラムダ式が使えない版は、ちゃんとJDK8)

The Lambda project has used source files that are not yet available in JDK8; therefore, these preview builds are created using the latest OpenJDK 7 source repository.

Java Platform, Standard Edition 8 Developer Preview Releases — Java.net

Javaのラムダ式

ラムダ式の記述方法は、古い資料だと

#{ Person p -> p.getLastName() }

みたいになってますが、今は

p -> p.getLastName()

みたいです。
#や{}が不要です。
(最初知らなくて散々コンパイルエラー出した…)


重要なポイントがあります。
Project Lambdaは、Javaにクロージャを導入するものではないようです。
あくまで、ラムダ式を導入するものでしかない、ということです。


クロージャの定義として、「レキシカルスコープを伴うファーストクラスの関数」を挙げました。
Project Lamdaでは、これを扱えないようです。

27400 Project Lambda: To Multicore and Beyond

セッションを担当したのは Alex Buxter でした。今年は Brian Goetz はセッションを持っていないんですよね。

さて、最初に書いておきますが、Java の Lambda 式はクロージャではないです。

Project Lambda はタイトルにもあるようにマルチコア時代のソフトウェアを効率よく記述するために仕様が策定されています。セッションでもまずそのことに言及されています。

Java SE 8 では Lambda 式だけではなく、型推論の向上や、メソッドへの参照なども含まれています。

Java in the Box Annex: 2011/10

SAM Type

Javaでクロージャを導入する、という話が出てから、紆余曲折ありました。
少なくともProject Lambdaでは、ラムダ式が書ける、というだけです。
Javaに元々クロージャはなかったですが、無名クラスを作って、
そこにインスタンス変数を定義すれば、近いことができます。


そして、記述の煩雑さに目を向ければ、
1つしかメソッドを持たないインタフェースを実装するクラスを
作成する機会が多いことに気づきます。


たった1つのメソッドを実装するためだけに、
あれだけ記述しなければならないのは、
大きな手間だと言えるでしょう。


こうした、メソッドを1つしか持たないインタフェースや抽象クラスを
「SAM Type」と呼ぶそうです。

「メソッドがひとつしかない抽象クラス」を特別扱いしよう,というだけです。
このような抽象クラス(もしくはインタフェース)を「single-abstract-method types」,「SAM Type」と提案書で呼んでいます。

Javaのラムダ式導入に関する最新提案の雑なまとめ - 矢野勉のはてな日記

OpenJDKのMLにも流れていました。

> Many existing Java libraries define interfaces which declare just
one method
> or abstract classes which declare just one abstract method (so-
called “SAM” types).

SAM types and functions

せっかくなので、その提案書も見てみました。

We call such classes and interfaces single-abstract-method types, or SAM types for short.
SAM types include Runnable, Callable, Comparator, and TimerTask.

concise instance creation expressions

Unfortunately, the verbose and ungainly syntax of anonymous class instance creation expressions frustrates programmers.

concise instance creation expressions


というわけで、Project Lambdaは、
クロージャではなく、記述の簡略化が目的ですね。

実行してみる

まえがきが長くなりました。
さっそく、簡単なラムダ式を書いてみましょう。

import java.util.*;

public class LambdaSample {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[] {1,2,3,4,5});
        Iterable<Integer> filtered =  list.filter( i -> 2 < i );
        System.out.println(filtered);

        String[] names = new String[] {"Alice", "Bob", "Charly"};

        final Comparator<String> c = (s1, s2) -> s1.length() - s2.length();
        Arrays.sort(names, c);
        System.out.println(Arrays.toString(names));

        Arrays.sort(names, (s1, s2) -> s2.length() - s1.length());
        System.out.println(Arrays.toString(names));


    }

}

実行すると、こう出力します。

$ java LambdaSample 
[3, 4, 5]
[Bob, Alice, Charly]
[Charly, Alice, Bob]

7行目の、List#filter()メソッドの引数がラムダです。
「 i -> 2 < i 」というラムダ式を書いています。
リストの各要素が、変数iに入り、
「2 < i」を満たした要素だけが入った、
新しいIterableインスタンスを返します。

次の例は、こちらのサイトを参照させていただきました。


Blogging at the speed of thought » Lambda expressions in Java 8 adopts C# style syntax
http://aruld.info/lambda-expressions-in-java-8-adopts-c-style-syntax/


1つは、Comparatorインタフェースを実装するインスタンスを、ラムダ式を使って生成する方法と、
もう1つは、Arrays.sort()メソッドの引数に、ラムダを渡す方法です。
Comparatorインタフェースは、前述したSAM Typeであり、
今までの無名クラスに比べて、簡単に記述することができます。


次の例も、先ほどのサイトから参照させていただいています。

public class SimpleLambda {
  interface HelloWorld {
    void greet();
  }
 
  {
    HelloWorld greeting = () -> System.out.println("Hello World!");
    greeting.greet();
  }
 
  public static void main(String... args) {
    new SimpleLambda();
  }
}

実行すると、こう出力します。

$ java SimpleLambda 
Hello World!

HelloWorldインタフェースは、greet()メソッドだけを持つ
SAM Typeです。


「() -> System.out.println("Hello World!")」で、
引数なしのメソッドで、Hello World!を出力するラムダ式を書いています。
greet()メソッド1つしか持たないインタフェースなので、
このラムダ式がgreet()メソッドの実装となり、
HelloWorldを実装したインスタンスを生成します。


次の例も、またまた先ほどのサイトから参照させていただいていますが、
型引数を取ることもできます。

public class SamLambda {
    interface Addable<X, Y, R> {
        R add(X x, Y y);
    }

    public static void main(String[] args) {
        Addable<Integer, Integer, String> a = (x, y) -> "result is " + String.valueOf(x + y);
        System.out.println(a.add(1, 2));
    }
}

実行すると、こう出力します。

$ java SamLambda 
result is 3

型引数が渡せるという点で、
内容自体は1つ前のサンプルと変わらないですね。

結局

Javaにクロージャは入らないってこと!?
検索はしてみたんですが、結論はわからず…