今年の 9/9 にリリースされると言われている Java 8 で注目している lambda を試してみました。
まずは lambda が使える JDK の入手からです。2013-03-15 現在 2種類のバイナリがありますので注意が必要です。入手先はこちら。今回は b81 を使っています。java -version
コマンドの実行結果は以下のとおりです。
1 2 3 4 |
$ java -version openjdk version "1.8.0-ea" OpenJDK Runtime Environment (build 1.8.0-ea-lambda-nightly-h3673-20130312-b81-b00) OpenJDK 64-Bit Server VM (build 25.0-b21, mixed mode) |
lambda の使いどころはいくつかあるのですが、一番の使いどころは配列やコレクションの操作ではないかと思います。以前のエントリでも紹介した以下のようなコードです。
1 2 3 |
students.filter((Students s) -> s.getGradYear() == 2012) .map((Students s) -> s.getScore()) .max(); |
現在はコレクションクラスに filter や map などのメソッドはなく java.util.stream.Stream
インタフェースに移っています。そのため配列やコレクションクラスを java.util.stream.Stream
に変換してやる必要があります。配列の場合は java.util.Arrays#stream()
、コレクションクラスの場合は自身の stream()
メソッドを呼び出します。API を見てみると parallelStream()
というメソッドもあり簡単に並列処理が出来るようになっています。簡単な検証コードを書いてみてベンチマークをして比較してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; public class Bench { private List initStars(int num) { Random random = new Random(); List stars = new ArrayList(); for (int i = 0; i < num; i++) { stars.add(new Star(String.valueOf(i), random.nextInt(Integer.MAX_VALUE))); } return stars; } private int maxDistance(Stream<Star> stars) { return stars.map(p -> p.distance).reduce(0, (x, y) -> x > y ? x : y); } private void printResult(long start, long stop, int maxDistance) { System.out.println("Max distance is " + maxDistance); System.out.println("It takes " + TimeUnit.NANOSECONDS.toMillis(stop - start) + " [ms]"); } public void start() { List<Star> stars = initStars(10_000_000); long start = System.nanoTime(); int maxDistance = maxDistance(stars.stream()); printResult(start, System.nanoTime(), maxDistance); start = System.nanoTime(); maxDistance = maxDistance(stars.parallelStream()); printResult(start, System.nanoTime(), maxDistance); } public static void main(String... args) { new Bench().start(); } private class Star { public final String name; public final int distance; public Star(String name, int distance) { this.name = name; this.distance = distance; } } } |
実行結果は以下のとおりです。何度か実行してみましたが上記のコードだと並列処理にすると半分程度の処理時間になりました。
1 2 3 4 5 |
$ javac Bench.java && java Bench Max distance is 2147483222 It takes 113 [ms] Max distance is 2147483222 It takes 57 [ms] |
上記のコードのメインは以下の部分です。
1 |
stars.stream().map(p -> p.distance).reduce(0, (x, y) -> x > y ? x : y); |
型推論が働くため型を省略して書いていますが、以下のようにして型を省略せずに書くことも可能です。
1 |
stars.stream().map((Person p) -> p.distance).reduce(0, (int x, int y) -> x > y ? x : y); |
lambda 式を指定できるところにはメソッドを指定することも可能です。
1 |
stars.stream().map(p -> p.distance).reduce(0, Math::max); |
最大値を求めることは一般的なので max()
メソッドも用意されています。
1 |
stars.stream().map(p -> p.distance).max().orElse(0); |
これまでの Java とは比べ物にならないくらいすっきりと書けるようになりました。関数型言語に慣れていない方にとっては一見良くわからないかもしれませんが、慣れると読みやすいですし、これなしではやってられないくらい強力です。Stream
には filter()
や limit()
などよく使いそうなメソッドが他にもありますので是非 API を確認してみてください。
その他よく使いそうなのは forEach()
メソッドですね。こちらは java.lang.Iterable
に定義されているメソッドなのでコレクションクラスからそのままで呼べます。
1 |
java.util.Arrays.asList("foo", "bar", "baz").forEach(e -> System.out.println(e)); |
こうなってくると System.out.println
の重厚さ (?) がアンバランスな感じです。ともあれ Java でも lambda が使えるのは喜ばしいことですね。