ラムダ式は超・簡単にいうと「名前のない小さなメソッド(匿名メソッド)」のことです。主に短い処理を簡潔に書くために使います。
ラムダ式が登場するまでは、ちょっとした処理のためにもクラスやインターフェースを用意しないといけませんでした。これは面倒でコードも複雑化します。
ラムダ式を使うことで以下のメリットがあります。
ラムダ式の基本的な書き方
ラムダ式の基本形は次のようになります。
(引数) -> { 処理内容 };
()
:引数を指定します(引数がない場合は空にします)->
:「引数を使って次の処理をする」という意味{}
:実際の処理を書きます
例①(引数がないラムダ式)
Runnable r = () -> System.out.println("Hello World"); r.run(); // Hello World
例②(引数があるラムダ式)
interface Printable { void print(String message); } Printable p = (msg) -> System.out.println(msg); p.print("ラムダ式を学ぼう!"); // ラムダ式を学ぼう!
ローカルクラス、匿名クラス、ラムダ式の流れで理解する
- ローカルクラス:使う場所(メソッドの中)でだけ有効なクラスを定義
- 匿名クラス:クラス名すらなくなる →「その場でしか使わないから、名前いらない」
- ラムダ式:匿名クラスを、さらにぐっと短く書ける(しかも関数型インターフェース限定)
通常のクラス → ローカルクラス → 匿名クラス → ラムダ式 (MyClass) (メソッド内で使う) (クラス名なし) (一番シンプル)
ローカルクラスで書いてみる
「このメソッドの中でだけ使うクラス」を作る:
public class Main { public static void main(String[] args) { class HelloPrinter { void print(String name) { System.out.println("Hello, " + name); } } HelloPrinter printer = new HelloPrinter(); printer.print("Alice"); // Hello, Alice } }
- クラス名:
HelloPrinter
- メソッド内でしか使わないクラス
- 「Alice」を表示するだけ
見てのとおり、「ちょっとの処理なのに、クラス宣言が長い」と感じませんか?
匿名クラスで書くとどうなるの?
public class Main { public static void main(String[] args) { // まずはインターフェース interface Printable { void print(String name); } // 匿名クラス Printable printer = new Printable() { @Override public void print(String name) { System.out.println("Hello, " + name); } }; printer.print("Bob"); // Hello, Bob } }
- メリット:ローカルクラスのようにクラス宣言(HelloPrinter)をしなくて良い
- デメリット:「new Printable() {…}」の中がまだ長い
ラムダ式でさらに短縮
public class Main { public static void main(String[] args) { interface Printable { void print(String name); } // ラムダ式 Printable printer = (name) -> { System.out.println("Hello, " + name); }; printer.print("Charlie"); // Hello, Charlie } }
- 超ポイント:「匿名クラス」→「ラムダ式」は、結局は同じ動きをする
- ただしラムダ式は、メソッドが1つしかないインターフェース(関数型インターフェース)に対してのみ使える
関数型インターフェース
ラムダ式は「関数型インターフェース」とセットで使います。
関数型インターフェース(Functional Interface)とは「抽象メソッドが1つだけ定義されているインターフェース」のことです。Java 8でラムダ式が導入された際に、「ラムダ式をどのようにしてメソッドに対応させるか」を実現するために、この関数型インターフェースが重要な役割を担うようになりました。
抽象メソッドがひとつだけ(Single Abstract Method: SAM)
関数型インターフェースは、「抽象メソッドがちょうど1つ」という性質を持ちます。
@FunctionalInterface interface MyFunction { // 抽象メソッド(1つだけ) int apply(int x, int y); // defaultメソッドはOK default void doSomething() { System.out.println("default method is allowed"); } }
apply(int x, int y)
が唯一の抽象メソッドdefault
メソッドやstatic
メソッドはいくつ追加してもかまいません- Javaコンパイラは、
@FunctionalInterface
アノテーションがついているインターフェースに、抽象メソッドが複数あるとエラーにします(「1つだけの抽象メソッドじゃないよ」と教えてくれる)
ラムダ式とセットで使う
Javaのラムダ式は、関数型インターフェースを実装する匿名クラスの短縮形とみることができます。
例えば以下のように、MyFunction
インターフェースをラムダ式で実装する例を見てみましょう。
public class Main { public static void main(String[] args) { MyFunction f = (x, y) -> x + y; // ラムダ式 int result = f.apply(5, 3); System.out.println(result); // 8 } }
(x, y) -> x + y
はMyFunction
の抽象メソッドapply
を定義しているイメージ- 「引数
x
,y
を足し算して返すよ」という処理を、一瞬で書けるメリットがあります
Java標準ライブラリの代表的な関数型インターフェース
Java 8以降、標準ライブラリに多くの関数型インターフェースが追加されました。
最もよく使われるものを挙げると次の4つです:
インターフェース | 説明 | 抽象メソッド例 |
---|---|---|
Runnable | 引数も戻り値もない処理 | run() |
Consumer | 引数を受け取るが戻り値なし | accept(T t) |
Function<T,R> | 引数と戻り値がある | apply(T t) |
Predicate | 引数を受け取りbooleanを返す | test(T t) |
import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; public class FunctionalInterfaceDemo { public static void main(String[] args) { // 1) Consumer<T> Consumer<String> c = (str) -> System.out.println(str); c.accept("Hello Consumer"); // Hello Consumer // 2) Function<T,R> Function<Integer, String> f = (num) -> "Value is " + num; System.out.println(f.apply(10)); // Value is 10 // 3) Predicate<T> Predicate<Integer> p = (n) -> n % 2 == 0; System.out.println(p.test(4)); // true System.out.println(p.test(5)); // false // 4) Supplier<T> Supplier<Double> s = () -> Math.random(); System.out.println(s.get()); // 0.1234567... など } }