ラムダ式は超・簡単にいうと「名前のない小さなメソッド(匿名メソッド)」のことです。主に短い処理を簡潔に書くために使います。
ラムダ式が登場するまでは、ちょっとした処理のためにもクラスやインターフェースを用意しないといけませんでした。これは面倒でコードも複雑化します。
ラムダ式を使うことで以下のメリットがあります。
ラムダ式の基本的な書き方
ラムダ式の基本形は次のようになります。
(引数) -> { 処理内容 };
():引数を指定します(引数がない場合は空にします)->:「引数を使って次の処理をする」という意味{}:実際の処理を書きます
例①(引数がないラムダ式)
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... など
}
}
