29.

Java ラムダ式完全ガイド(Functional Interface / Method Reference / Stream)

編集
この記事の要点
  • Java ラムダ式: (x, y) -> x + y。Java 8+ で導入
  • ラムダの型は関数型インタフェース(Functional Interface = SAM, 抽象メソッド 1 個)
  • 標準提供: Function<T,R> / Predicate<T> / Consumer<T> / Supplier<T> / BiFunction<T,U,R>
  • Method Reference: String::length / System.out::println でラムダを簡潔に
  • ラムダから参照する外部変数はeffectively final である必要(事実上 final)

ラムダ式の基本

// 引数なし
Runnable r = () -> System.out.println("Hello");

// 引数 1 個(括弧省略可)
Function<Integer, Integer> square = x -> x * x;

// 引数 2 個
BinaryOperator<Integer> add = (x, y) -> x + y;

// 複数行はブロックと return
Function<Integer, String> classify = n -> {
    if (n > 0) return "positive";
    if (n < 0) return "negative";
    return "zero";
};

// 型を明示することも可能
Function<Integer, Integer> f = (Integer x) -> x * 2;

Functional Interface(関数型インタフェース)

ラムダの型は抽象メソッドを 1 つだけ持つインタフェース(SAM: Single Abstract Method)。コンパイラがそのメソッドにマッピングします。

@FunctionalInterface
interface StringTransformer {
    String transform(String input);
    // default / static メソッドは持ってよい
    default String transformUpper(String s) {
        return transform(s).toUpperCase();
    }
}

StringTransformer reverse = s -> new StringBuilder(s).reverse().toString();
System.out.println(reverse.transform("hello"));  // olleh

// @FunctionalInterface はチェック用アノテーション
// 抽象メソッドが 2 個以上だとコンパイルエラー

標準の関数型インタフェース(java.util.function)

インタフェースシグネチャ用途
Function<T,R>R apply(T t)T → R の変換
BiFunction<T,U,R>R apply(T t, U u)2 引数
Predicate<T>boolean test(T t)真偽判定(フィルタ)
Consumer<T>void accept(T t)副作用のみ(forEach 等)
Supplier<T>T get()遅延生成 / ファクトリ
UnaryOperator<T>T apply(T t)T → T (Function 特殊版)
BinaryOperator<T>T apply(T t1, T t2)(T, T) → T (sum 等)
import java.util.function.*;

Function<String, Integer> length = String::length;
Predicate<String> isEmpty = String::isEmpty;
Consumer<String> printer = System.out::println;
Supplier<List<String>> newList = ArrayList::new;
BiFunction<Integer, Integer, Integer> max = Math::max;

System.out.println(length.apply("hello"));   // 5
System.out.println(isEmpty.test(""));        // true
printer.accept("hello");                     // hello
System.out.println(max.apply(3, 7));         // 7

Method Reference(メソッド参照)

種類ラムダ等価
static メソッドInteger::parseInts -> Integer.parseInt(s)
インスタンスメソッド(特定)System.out::printlnx -> System.out.println(x)
インスタンスメソッド(任意)String::lengths -> s.length()
コンストラクタArrayList::new() -> new ArrayList()

effectively final(事実上 final)

ラムダから外側のローカル変数を参照する場合、その変数はfinal または事実上 final(一度しか代入されない)でなければなりません。

public void demo() {
    int base = 10;                          // effectively final(再代入なし)
    Function<Integer, Integer> f = x -> x + base;  // OK

    int counter = 0;
    // counter++;  // ❌ これを書くと f がコンパイルエラー
    Runnable r = () -> System.out.println(counter);

    // インスタンス変数 (this.field) は制限なし
    this.value = 100;
}

// 回避策: 配列 or AtomicInteger
int[] counter = {0};
Runnable r = () -> counter[0]++;

Stream API での活用

List<String> names = List.of("taro", "jiro", "saburo", "shiro");

String result = names.stream()
    .filter(s -> s.length() > 4)                  // Predicate
    .map(String::toUpperCase)                     // Function (Method Reference)
    .sorted(Comparator.comparingInt(String::length))
    .collect(Collectors.joining(", "));

System.out.println(result);  // SABURO, SHIRO

無名内部クラスとの違い

// Java 7 以前: 無名内部クラス
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Java 8+: ラムダ
Runnable r2 = () -> System.out.println("Hello");
項目無名内部クラスラムダ
this の意味無名クラスのインスタンス★ 囲み側の this
クラスファイル追加で生成されるinvokedynamic で軽量
抽象メソッド数制限なし1 つだけ(SAM)
外側変数アクセスfinal 必須(Java 7 まで)effectively final

他言語との比較

Java:    (x, y) -> x + y
Python:  lambda x, y: x + y
PHP 7+:  fn ($x, $y) => $x + $y          // arrow function
PHP:     function ($x, $y) { return $x + $y; }
JS:      (x, y) => x + y                  // ES6 arrow
Kotlin:  { x, y -> x + y }
C#:      (x, y) => x + y
Scala:   (x: Int, y: Int) => x + y

PHP の arrow functions との対応

// PHP 7.4+ arrow function
$add = fn ($x, $y) => $x + $y;
echo $add(1, 2);  // 3

// 外側変数を自動キャプチャ(use は不要)
$base = 10;
$addBase = fn ($x) => $x + $base;

// 通常の無名関数では use が必要
$addBase2 = function ($x) use ($base) {
    return $x + $base;
};

FAQ

Q: ラムダ式に clone はある?
A: ラムダは Cloneable ではなく clone() 呼び出しは未定義動作です。状態を持たない関数として扱ってください。

Q: ラムダで例外を投げたい(チェック例外)
A: 標準 Functional Interface はチェック例外を投げられません。独自の @FunctionalInterface を定義するか、ラムダ内で try/catch して RuntimeException でラップ。

Q: ラムダのデバッグがしづらい
A: スタックトレースに lambda$method$0 のような自動生成名が出ます。複雑なロジックは private メソッドに切り出して Method Reference 化を推奨。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. データ型
  3. 変数
  4. 定数
  5. 配列
  6. コレクション(List,Set,Queue)
  7. Map(連想配列)
  8. 演算子
  9. 条件分岐
  10. 繰り返し制御文
  11. クラス
  12. メソッド
  13. インスタンス化
  14. コンストラクタ
  15. staticキーワード
  16. オーバーロード
  17. 継承
  18. オーバーライド
  19. this
  20. super
  21. パッケージ
  22. アクセス修飾子
  23. 抽象クラス・メソッド
  24. インターフェース
  25. カプセル化
  26. データベース接続
  27. セッション
  28. ファイル入出力
  29. ラムダ式

最近更新/作成されたページ