はじめに
今までラムダ式はコードを簡潔に書ける便利なものくらいの理解でしかなかったので、今回はラムダ式を分解しながら克服していこうと思います。
ラムダ式とは
ラムダ式(lambda expression)とは、プログラミング言語に用意された記法の一つで、名前の無い無名関数(匿名関数)を簡潔に定義するもの。
よくわかりません。実際にコードで理解していきます。
前提知識
ラムダ式はローカルクラスと無名クラスという仕組みを利用しています。
ローカルクラスとは
ローカルクラスはメソッド内で宣言するクラスのことです。
インターフェースを実装したローカルクラスも定義できます。
public static void main(String[] args) {
class Local implements Runnable {
@Override
public void run() {
System.out.println("Hello Lambda!");
}
}
Runnable runner = new Local();
runner.run(); // Hello Lambda!
}
無名(匿名)クラスとは
無名クラスは、インターフェースや抽象クラスのインスタンスを直接生成するためのクラスです。
public static void main(String[] args) {
Runnable runner = new Runnable() {
@Override
public void run() {
System.out.println("Hello Lambda!");
}
};
runner.run(); //Hello Lambda!
}
ラムダ式とは
無名クラスから更に「new Runnable(){}」と「public void run」を省略してラムダ式となります。
public static void main(String[] args) {
Runnable runner = () -> { System.out.println("Hello Lambda!"); };
runner.run(); //Hello Lambda!
}
最初の()はrunメソッドの引数を表し、->{}の中身はrunメソッドの実装内容になります。
runner変数にはRunnableを実装した無名クラスのインスタンスが代入されます。
つまり、ラムダ式とはインターフェースを実装したインスタンスを生成する式といえます。
ところで「new Runnable(){}」を省略したら、何型のインスタンスを生成するのかわかりません。
Javaでは代入される変数の型によって自動的に推論する仕組みになっています。
この仕組みを型推論と呼びます。
また、「public void run」を省略すると、複数メソッドが定義されているインターフェースの場合、どのメソッドをオーバーライドするのかわかりません。
そのため、ラムダ式で使用できるのは抽象メソッドが一つのインターフェースのみとなります。
Rannableインターフェースだけでは引数なし、戻り値なしのラムダ式しか作れません。
他の形で作成したい場合は、関数型インターフェースを利用します。
関数型インターフェースとは
関数型インターフェースとは、抽象メソッドが一つだけあるインターフェースのことです。
default, static, privateメソッドは含まれていても問題ありません。
例えば以下のようなインターフェースがあります。
Function< T,R >
Functionは、値を変換する為の関数型インターフェース。
Function< T,R >のTはメソッドの引数の型、Rは戻り値の型を指定します。
引数を受け取り、変換(演算)して別の値を返す。
メソッドは R apply(T) です。
Function<Integer, String> asterisker = (i) -> { return "*"+ i; };
String result = asterisker.apply(10);
System.out.println(result); // *10
Consumer< T >
Consumerは、引数を受け取り、それを使って処理を行う為の関数型インターフェース。
ConsumerのTはメソッドの引数の型を指定します。
値を返さないので、基本的に副作用を起こす目的で使用する。
メソッドは void accept(T) です。
Consumer<String> buyer = (goods) -> { System.out.println(goods + "を購入しました"); };
buyer.accept("パソコン"); // パソコンを購入しました。
Predicate< T >
Predicateは判定を行う為の関数型インターフェース。
PredicateのTはメソッドの引数の型を指定します。
引数を受け取り、判定を行い、真偽値(判定結果)を返す。
メソッドは boolean test(T) です。
Predicate<String> checker = (s)-> { return s.equals("Java"); };
boolean result = checker.test("Java");
System.out.println(result); //true
ラムダ式のメリット
次のコードはラムダ式で書かれたものです。
List<Integer> numbers = List.of(3, 1, -4, 1, -5, 9, -2, 6, 5, 3, 5);
numbers.stream()
.filter(number -> Math.abs(number) >= 5)
.forEach(System.out::println);
出力結果は以下
-5
9
6
5
5
非常にシンプルです。
ラムダ式を利用しない場合は以下のようになります。
List<Integer> numbers = List.of(3, 1, -4, 1, -5, 9, -2, 6, 5, 3, 5);
numbers.stream()
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer number) {
return Math.abs(number) >= 5;
}
})
.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer number) {
System.out.println(number);
}
});
コードの記述量が増え、見づらくなりました。
ラムダ式を用いれば、クラスの定義とインスタンスの生成を合わせて行うことができますので、コードの記述がシンプルになるメリットがあり、簡潔でわかりやすいコードを書くことができます。
まとめ
今回記事作成を通してラムダ式を克服できた気がします笑
ざっくり説明しましたが、より詳しいラムダ式文法など気になる方はぜひ調べてみてください!