lambda 式
はじめに
まずですが、一般に lambda 計算(wiki page)と呼ばれるものがあり、その中で lambda 式 (lambda expression) と呼ばれるものが使われています。
λ x.(x+1)
と書くと、x を引数にとり、x+1 を返す関数という感じです。(正確には、+ などの演算子がない方が、純粋な lambda 計算だと言えますが。)
Lisp 系の言語では (lambda (x) (+ x 1) とか書いたりもします。
関数型プログラミング言語では、こういった関数を「値」として扱うことができるのが特徴です。
一方で、従来の Java 言語では、interface やクラス階層を用いることで、「指定メソッドを備えたオブジェクトとそのpolymorphism」として、関数的なものを「オブジェクト」として扱ってきました。
Java 8 から導入された lambda expression は、一つしかメソッドをもたないような interface を簡潔表現する手段として、導入されました。
同時に、各種ライブラリに、関数型言語の考え方が色々流入しはじめています。
Comparator の場合
java.util.Comparatorint compare(T o1, To2) というメソッドを一つ持つだけです。
annonymous inner class をつかうとこんな感じですよね(Comparator<Integer> の場合)。
new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a, b);
}
}
lambda expression をつかうと、こんな感じ(ブロック文の場合)
Comparator<Integer> comp2 = (a, b)->{
return Integer.compare(a, b);
}
か、あるいは返り値部を式にして
Comparator<Integer> comp = (a, b)->Integer.compare(a, b);
のように書いても OK です。(a, b) のところは、(Integer a, Integer b) と書いてもいいのですが、
最近は「推論できる」型情報はいろいろ省略可能になっています。
変数への参照
lambda expression ですが、関数ないで外部の変数へのアクセスが可能なので、ある意味「状態」を持ちます。 つまり、プログラム(関数)という側面だけでなく、状態(オブジェクト)としての側面もあるわけです。
例えば、与えられた変数 x に近い値から順にソートしたかったとします。Comparator は x の値をもってないと、x に近いか判断できないですよね。
final int target = 200;
list.sort((a, b)->{
return Integer.compare(Math.abs(a-target), Math.abs(b-target));
});
例えば、上記の cmp は、関数内で a - target など、lambda 式外の変数 target にアクセスしています。できあがった Comparator は sort メソッドに渡されて使われるわけですが、呼ばれた関数(sort)側では、どっかの変数に代入して隠し持っておいたり、好きに使うことができる独立した存在です。いつでも target にアクセスできるように変数を保持したオブジェクトのような存在となっています。
ちなみに、target の値が途中で代入して変更された場合、もともと(lambda 外)の target と lambda (オブジェクト)内のtarget が整合性がなって混乱を生じるので、lambda や inner class が外部の局所変数にアクセスできるのは、final宣言された、更新を伴わない変数やthis へのアクセスだけが許されています。この場合は、lambda や inner class は、外側の変数の「値」をコピーしてもっておけばOKってことになります。
default method
lambda expressions では、メソッドが一つしか定義できませんが、interface を使っていると、interface で定義されたメソッドを使って、関連するメソッドを自動で作成したいケースもあります。
例えば、java.util.Comparatorの場合、thenComparing(Comparator other) というメソッドがあるのですが、これは、
- 「自身(
this)で比較して同じ値なら、otherでも比較してその値を使う」ような比較器
を作るメソッドです。比較器と比較器を組み合わせて、合成した比較器をつくるような感じですね。 関数型プログラミングでは、「関数」的なものを「値」として捉えて、「関数」を合成するようなことをします。 便利ですが、馴染めない人も多いかもしれませんね。
で、default method というのは、interfaceにメソッドを定義しておくと、interface を implement 宣言するだけで、実装クラスに default method を組み込むことができます。
先ほどのthenComparing の実装を覗いてみると、こんな感じになっています(javaSE-1.8からの引用)。
確かに、「自身で比較して」、「一緒だったら other で比較した結果を返す」ような、Comparator を返してますよね。
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
で、こんな感じのメソッドが使えると、例えばこんな使い方ができます(UseSampleP1の例)。 少し、利用メソッドの種類が増えていますが。
Comparator<MyPoint> cmp =
Comparator.comparing(MyPoint::sum)
.thenComparingInt((MyPoint o) -> o.y)
.reversed();
Comparator.comparing(MyPoint::sum)はMyPointのsum()の値で大小比較する比較器MyPoint::sumは、(MyPoint p)->sum()相当
.thenComparingInt(...)で、sum()が一緒の場合は「yの値で比較するような比較器」を試すような比較器を合成し、- で、上記の比較器の順番を逆転させるような比較器を生成する。
「比較器」を「値」のように、合成したり変換したりする、関数型プログラミングの感覚を感じでもらえましたでしょうか。
ちなみに、implements interfaceA とか書くだけでメソッドが追加されるので、implements interfaceA, interfaceB とか書くと、複数のメソッドが重複して困ったことになるかもしれません。そんな時はちゃんとコンパイラが怒ってくれます。
forEach method
lambda expression が簡単に使えるようになったので、いろんな用途で使用機会が増えています。
一例として、forEach メソッドを紹介しておきます。
list.forEach((elem) -> {
System.out.println(elem);
});
list の各要素に、引数として与えられた lambda expression (実際は、java.util.function.Consumer というinterface)を適用するというメソッドです。for文で書いていたものが、method 呼出しで書けるようになりました。
forEach メソッドですが、java.lang.Iterable の default method でして、java.util.ArrayList などの様々な Collection で利用可能です。