Javaラムダ式
はじめに
サーバサイドエンジニアのliitFeetです。最近はもっぱらkotlinとSpringBootでWebFluxをCoroutineって感じです。
Coroutine難しい・・・
この記事はチームラボエンジニアリングアドベントカレンダー7日目の記事です。
この記事ではJavaのラムダ式の成り立ちについて説明していきます。
え、今更?っていう感じはありますが、goやkotlinなど関数を第一級オブジェクトとして扱えるようになって、実はラムダ式の中身が何かよくわからずに使っていることありそうだなーと思い書きました。
結論から言うと、javaのラムダ式はメソッドが一つしかないインターフェースを実装してインスタンスを作成(new)します。
多分この説明ではなんのこっちゃわからないと思いますので、ある式を計算した結果を表示するプログラムを例に、ラムダ式までの成り立ちを順をおって説明していきます。
- 通常のクラスを引数で受け渡し
- インナークラスを引数で受け渡し
- 無名クラスを引数で受け渡し
- ラムダ式を引数で受け渡し
ラムダ式は記述量を減らしつつ、人間にもコンパイラーにも読みやすい記述法なんだなーというのを意識するとわかりやすいかなと思います。
また、この記事はJavaのラムダ式の成り立ちについて説明しますが、
ラムダ式自体の詳しい説明(引数が一つだけならカッコは記述不要や関数型インターフェースなど)はしませんのでご了承ください〜。
ソースコードはGitHubにあります。
通常のクラスを引数で受け渡し
まずはノーマルなクラスを受け渡すことで、計算した結果を表示するプログラムを実現したいと思います。
ソースコードはnormalClassパッケージです。
ソースは下記の4つです。
Main.java メインのクラス
Display.java 計算結果を表示するクラス
CalcuratorInterface.java 計算について定義したインターフェース
Display.java CalcuratorInterface
まずDisplayクラスでは、計算を担当するcalculatorと計算する値(a, b)を受け取り、
calculatorで(a, b)の値を計算して表示します。
public class Display {
public void showCalc(CalculatorInterface calculator, int a, int b) {
System.out.println(calculator.calc(a, b));
}
}
CalculatorInterfaceでは計算を行うメソッド、calcを定義します。
public interface CalculatorInterface {
int calc(int a, int b);
}
AddCalculatorではCalculatorInterfaceを実装して、引数(a, b)を足した値を返します。
public class AddCalculator implements CalculatorInterface {
@Override
public int calc(int a, int b) {
return a + b;
}
}
MainクラスではDisplayクラスに計算を行うcalculator
と計算する値を渡して計算結果を表示させています。
public class Main {
public static void main(String[] args) {
Display display = new Display();
CalculatorInterface calculator = new AddCalculator();
display.showCalc(calculator, 3, 5);
}
}
特に問題ないかと思います。
インナークラスで受け渡し
次に、インナークラスを使ってみたいと思います。
ソースコードはinnerClassパッケージです。
CalcuratorInterface.javaと、Display.javaは変更ありません。
前述の通常のクラスを使う場合、ただMainクラスだけで使う足し算をするだけのために、わざわざクラスファイルを作成するのは面倒だと思いませんか。
そこで、以下のようにMainの中でAddCalculatorを定義してしまい、AddCalculator.javaを無くすことができます。
public class Main {
public static void main(String[] args) {
Display display = new Display();
// インナークラスを定義
class AddCalculator implements CalculatorInterface {
@Override
public int calc(int a, int b) {
return a + b;
}
}
CalculatorInterface calculator = new AddCalculator();
display.showCalc(calculator, 3, 5);
}
}
無名クラスを関数で受け渡し
次に無名クラスを使っていきます。
ソースコードはanonymousInnerClassパッケージです。
インナークラスを使って、クラスファイルを一つ作らずにすみましたが、ここで一回しか使わないであれば、このクラスに名前つける必要はありません。
そこでcalculatorに代入するタイミングで、その場でクラスを実装してしまいましょう。
public class Main {
public static void main(String[] args) {
Display display = new Display();
// 一回なら使い捨てで
CalculatorInterface calculator = new CalculatorInterface() {
@Override
public int calc(int a, int b) {
return a + b;
}
};
display.showCalc(calculator, 3, 5);
}
}
これが無名クラスです。使い回しはできませんが、記述量がだんだん減ってきましたね。
ラムダ式
最後に、ラムダ式を使った引数の受け渡しです。
ソースコードはlambdaExpressionパッケージです。
無名クラスを使うことでクラスの名前を省略し、記述量を抑えることができました。
ですが、CalculatorInterfaceを実装するならnew CalculatorInterface()
と書かなくともコンパイラでよしなにしてくれそうです。
それに、メソッドが一つしかないなら、どのメソッドを実装するかも一意に決まっています。
メソッドの引数もインターフェースで型の定義がされているし、宣言しなくともコンパイラは型を推測することが可能です。
ラムダ式では、実装に必要なメソッドのみ記述することでインスタンスを生成することができます。
public class Main {
public static void main(String[] args) {
Display display = new Display();
// 実装するメソッドの処理のみかく
CalculatorInterface calculator = (a, b) -> {
return a + b;
};
display.showCalc(calculator, 3, 5);
}
}
ラムダ式は以下の部分ですね。この部分で、CalculatorInterfaceのcalcメソッドの実装を表現しています。
(a, b) -> {
return a + b;
};
(a, b)
は引数です。メソッドの引数はインターフェースで型の定義がされているのでわざわざ書きません。
->
以降がメソッドの実装です。a+b
をリターンしてますね。
まとめ
いかがでしたでしょうか。
最初に「javaのラムダ式はメソッドが一つしかないインターフェースを実装してインスタンスを作成(new)します。」と書いた意味が伝わったら幸いです!
完全に余談ですが、kotlinとかみたいに第一級オブジェクトとして関数扱えると楽でいいですよね・・・引数もわかりやすい・・・
val calculator = {a: Int, b: Int -> a + b}
showCalc(calculator, 3, 5)
fun showCalc(calc: (Int, Int) -> Int, a: Int, b: Int) {
println(calc.invoke(a, b))
}