PR

Java:ラムダ式(lambda)を3分でわかりやすく

Java

ラムダ式は超・簡単にいうと「名前のない小さなメソッド(匿名メソッド)」のことです。主に短い処理を簡潔に書くために使います。

ラムダ式が登場するまでは、ちょっとした処理のためにもクラスインターフェースを用意しないといけませんでした。これは面倒でコードも複雑化します。

ラムダ式を使うことで以下のメリットがあります。

  • コードがシンプルになる
  • 一度きりの処理が書きやすくなる
  • 読みやすく保守しやすいコードになる
スポンサーリンク

ラムダ式の基本的な書き方

ラムダ式の基本形は次のようになります。

(引数) -> { 処理内容 };
  • ():引数を指定します(引数がない場合は空にします)
  • ->:「引数を使って次の処理をする」という意味
  • {}:実際の処理を書きます

例①(引数がないラムダ式)

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 + yMyFunction の抽象メソッド 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... など
    }
}
タイトルとURLをコピーしました