これは何?
ふとした疑問で、Javaのラムダ式に比べ、Kotlinのラムダ式が書きやすく感じています。
いったい何が要因でそう感じるか。
頭の中を整理してたいと思います。
※JavaとシェルスクリプトとPythonぐらいしか触ったことのないユーザの戯言です。
※もし誤りございましたら、そっとご指摘いただけると大変うれしいです m(_ _)m
そもそもラムダ式とは?
無名関数の記述方法の一つ。
Javaでは無名関数を定義する場合、以下のように記載していました。
public class Main {
public static void main(String[] args) {
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println("Hello lambda.");
}
};
r.run(); // Hello lambda.
}
}
Java8からはラムダ式をサポートしたことで、より簡素に表現できるようになりました。
public class Main {
public static void main(String[] args) {
Runnable r = () -> { System.out.println("Hello lambda."); };
r.run();
}
}
一方、これをKotlinのラムダ式で表現すると以下の通りとなります。
public class Main {
fun main(args: Array<String>) {
val r = {print("Hello lambda")}
r()
}
}
Kotlinはラムダ式に引数不要の場合省略可能だったり、行末の「;」が不要だったりと細かい違いはありますが、
ほぼ同じように表現できます。
関数を受け取って関数を実行する関数
別の例で考えてみましょう。
2つの数値を受け取り、これに対し演算する関数を適用して返却する関数calcを定義してみましょう。
今度はKotlinから
public class Main {
fun main(args: Array<String>) {
val op = { a:Int, b:Int -> a+b}
calc(1,2,op) // 3
}
fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
}
先ほどと同じ感じで書けましたね。
では、Javaで同じように定義する場合、どうすればよいでしょうか?
.
..
...
....
関数型インタフェースのBiFunction を使えばよいですね。
import java.util.function.BiFunction;
public class Main {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b;
calc(1, 2, op); // 3
}
public static Integer calc(Integer a, Integer b, BiFunction<Integer, Integer, Integer> op){
return op.apply(a,b);
}
}
しかし、これって Javadoc を何も見ずに実装できたでしょうか?
私は、調べないと実装できませんでした。。
引数を3つ受け取り演算する関数を定義する
今度は引数を3つ受け取って演算するものを実装してみましょう。
まずはKotlinから。
public class Main {
fun main(args: Array<String>) {
val op = { a:Int, b:Int, c:Int -> a+b+c}
calc(1,2,3,op) // 6
}
fun calc(a: Int, b: Int, c: Int, op: (Int, Int, Int) -> Int): Int {
return op(a, b)
}
}
先ほどと比べ、引数cが増えていますがほぼ前回と同じ感じで書けました。
では、Javaではどうでしょうか?
前回はJavaの場合は BiFunction を使いました。
なので、同じノリで TriFunction を使えば良いかなと思います。
ですが... これは標準パッケージで提供されていません。
なので、下記例のように自分で関数型インタフェース(TriFunction)を定義してやる必要があります。
public class Main {
public static void main(String[] args) {
TriFunction<Integer, Integer, Integer, Integer> op = (a, b, c) -> a+b+c;
calc(1, 2, 3, op); //6
}
public static Integer calc(Integer a, Integer b, Integer c, TriFunction<Integer, Integer, Integer, Integer> op){
return op.apply(a,b,c);
}
@FunctionalInterface
interface TriFunction<A, B, C, R>{
R apply(A a, B b, C c);
}
}
ここまでで、いろいろ例を交えて比較しました。
つまり何が違うのかまとめましょう。
まとめ
JavaとKotlinのラムダ式においての書きやすさの違いは「関数が第一級関数として扱えるかどうか」が肝だと理解しています。
第一級関数(だいいっきゅうかんすう、英: first-class function、ファーストクラスファンクション)[1]とは、関数を第一級オブジェクトとして扱うことのできるプログラミング言語の性質、またはそのような関数のことである。
その場合その関数は、型のある言語では function type(en:Function type)などと呼ばれる型を持ち、またその値は関数オブジェクトなどになる。
具体的にはプログラムの実行時に生成され、データ構造に含めることができ、他の関数の引数として渡したり、戻り値として返したりすることのできる関数をいう。
引用:https://ja.wikipedia.org/wiki/%E7%AC%AC%E4%B8%80%E7%B4%9A%E9%96%A2%E6%95%B0
Kotlinの関数は第一級関数のため、あらかじめインタフェース/クラスが不要で関数を定義できます。
一方、Javaの関数は第一級関数ではないため、関数単体では扱えず入れ物となるインタフェースまたはクラスが必要となります。
したがって、今回のように関数を実装するにはそもそもインタフェースを調べたり、標準パッケージで用意されていない場合には自身で定義することが必要となってきます。
この差が、ラムダの書き心地の違いかなと考えています。
感想
この記事では、Javaが悪いと言いたいのではなく、漠然と感じてるもやもやした感情を整理したいという思いから作成しました。
やはり自分で噛み砕き、整理して行かないと理解力って深まりませんね。
今後もこういった姿勢は忘れずに行きたいと思います。
参考リンク
https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/lambdas.html
https://qiita.com/sano1202/items/64593e8e981e8d6439d3