ついにJavaにもクロージャ導入か!?という、注目のProject Lambdaのセッションです。
実際のところ、クロージャと言うより、匿名内部クラスよりは見やすい新たなラムダ式の記法ってだけなんですけども。
とりあえずメンドイし、厳密な定義とかあまりよく知らないので、新しいラムダの記法を「クロージャ」って書いときます。
なぜ今更Javaにクロージャを導入するのか?
ムーアの法則における成長限界もあるし、将来的にマルチコアによる並列処理が主流になってくるので、マルチコアをもっと引き出すような言語サポートが必要だよね、というのが主な動機だそうです。
確かに現状でも優秀なjava.util.concurrentパッケージがありますが、万人向けとは言い難い。もっと手軽に利用できて、かつ、コードが読みやすくなるようにしていかないと。
パラレル処理がかんたんに書けないなら、シーケンシャルに書いちゃうよね。
だって、にんげんだもの。
というわけで、Project Lambdaというのは、実はクロージャ記法導入だけを目的としてちっちゃなプロジェクトでは全然なくて、言語としての並行処理サポート向上を目的とした、割と壮大なプロジェクトだったのでした。
手始めに:内部イテレーション記法のサポート
for文とかIterator#next()とかでぐるぐるまわしつつ処理をするのを外部イテレーションとかいいます。配列とかコレクションを、それ自体ではない外側のプログラムコードでぐるぐる回すから「外部」。
それに対して、配列やコレクション自体に「おまえらの構成要素に○○という処理をやってくれよ」と処理をお願いして、自分自身でぐるぐる回してもらうのが「内部イテレーション」。
というわけで、コレクションにメソッドが追加されて↓という記法ができるようになります。
double highestScore = students.filter(new Predicate<Student>() { public boolean op(Student s) { return s.gradYear == 2010; } }).map(new Extractor<Student,Double>() { public Double extract(Student s) { return s.score; } }).max();
GroovyとかRubyとか知ってる人にとっては、構造は見慣れたものですけど、まだまだゴテゴテした感じに映りますね。
特に匿名内部クラスの冗長な記法といったら・・・。
クロージャの導入
ということで、単純にそういう冗長な記法はやめたいね、という動機で、クロージャ記法の導入となります。
↓こうなります。
double highestScore = students .filter(#{ Student s -> s.gradYear == 2010 }) .map( #{ Student s -> s.score }) .max();
頭に#が付く以外は、ほとんどGroovyと同じ記法ですね。
記法案は色々あるけど、「Syntax War: Just say no!」だそうです。おつかれさまです。
SAM型
1つの抽象メソッドを持つインタフェースor抽象クラスのことを、SAM型(Single Abstract Method types)と呼びます。
クロージャで記述できるのは、それらSAM型のクラスだけです。
つまり、↓とか。
interface Runnable { void run(); } interface Callable<T> { T call(); } interface Comparator<T> { boolean compare(T x, T y); } interface ActionListener { void actionPerformed(…); } abstract class TimerTask { … abstract void run(); … }
SAM型であれば、何か特別な宣言を追加しなくとも、この新しいクロージャで記述を代替できます。
つまり、既存のAPIも特別な対処をしなくても、この恩恵にあずかれるわけです。
メソッド呼び出し
ある意味ただのシンタックシュガーなので、
interface Predicate<T> { boolean isTrue(T); }
というSAM型に対して、
Predicate<Student> p = #{ Student s -> s.gradYear == 2010 };
とクロージャで記述したとしても、普通に
boolean ok = p.isTrue(aStudent);
のようにisTrueメソッド呼び出しができます。
引数の型推論
Predicate<Student> p = #{ s -> s.gradYear == 2010 };
と書くこともできます。
といっても、Groovyのようにどんなオブジェクトでも受け取れるvariantっぽい型になるわけではなくて、型推論によって結局静的に1つの型に決定されます。
上記の例だと、
Predicate<Student> p = #{ Student s -> s.gradYear == 2010 };
と書いた場合とまったく同じ意味になります。
ライブラリの改善(便利メソッド追加)
class Person { String getLastName() {…} }
List<Person> people = …
というPersonのリストに対して、ソートをしたいんだけど、
Collections.sort(people, new Comparator<Person>() { public int compare(Person a, Person b) { return a.getLastName().compareTo(b.getLastName()); } });
という記法が冗長すぎてイヤんなので、クロージャを使おう。
Collections.sort(people, #{ a,b -> a.getLastName().compareTo(b.getLastName()) });
こう書けるようになった。
が、しかし、getLastNameって書きすぎだろ。DRYじゃないよね。
というわけで、↓というExtractorクラス(SAM型)と新しいsortByメソッドを追加してみたら、
interface Extractor<T, U> { U get(T element); } public <T, U extends Comparable<…>> void sortBy(Collection<T> coll, Extractor<T,U> ex) {…} }
↓こう書けるようになったよ!
Collections.sortBy(people, #{ p -> p.getLastName() });
わかりやすいね!、というお話。
メソッド参照(Method References)
前項のsortByの最終形態ですが、クロージャ書くのすらメンドイので、もうちょい欲張って、
Collections.sortBy(people, #Person.getLastName);
こう書けるようにしてみました。
ってことで、導入された謎の記法がメソッド参照(Method references)。
#で始まって、クラス名 + "." + メソッド名 と書きます。 ()とか引数は不要です。
個人的にはなんかちょっとやりすぎ感が否めない...。
Extension methods
他言語のMixinやTraitに対するJavaの回答がこれ。
public interface Set<T> extends Collection<T> { public int size(); … public extension T reduce(Reducer<T> r) default Collections.<T>setReducer; }
ってことで、インタフェースにデフォルトの振る舞いをビルトインできるようになりました。
インタフェースの実装クラスでオーバーライドしなければ、defaultで指定していたクラスのメソッドが使用されます。
だいたいこんな感じです。はい。