タイトル: ラムダ式
SEOタイトル: Java ラムダ式完全ガイド(Functional Interface / Method Reference / Stream)
| この記事の要点 |
|
ラムダ式の基本
// 引数なし
Runnable r = () -> System.out.println("Hello");
// 引数 1 個(括弧省略可)
Function square = x -> x * x;
// 引数 2 個
BinaryOperator add = (x, y) -> x + y;
// 複数行はブロックと return
Function classify = n -> {
if (n > 0) return "positive";
if (n < 0) return "negative";
return "zero";
};
// 型を明示することも可能
Function 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 | R apply(T t) | T → R の変換 |
BiFunction | R apply(T t, U u) | 2 引数 |
Predicate | boolean test(T t) | 真偽判定(フィルタ) |
Consumer | void accept(T t) | 副作用のみ(forEach 等) |
Supplier | T get() | 遅延生成 / ファクトリ |
UnaryOperator | T apply(T t) | T → T (Function 特殊版) |
BinaryOperator | T apply(T t1, T t2) | (T, T) → T (sum 等) |
import java.util.function.*;
Function length = String::length;
Predicate isEmpty = String::isEmpty;
Consumer printer = System.out::println;
Supplier> newList = ArrayList::new;
BiFunction 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::parseInt | s -> Integer.parseInt(s) |
| インスタンスメソッド(特定) | System.out::println | x -> System.out.println(x) |
| インスタンスメソッド(任意) | String::length | s -> s.length() |
| コンストラクタ | ArrayList::new | () -> new ArrayList() |
effectively final(事実上 final)
ラムダから外側のローカル変数を参照する場合、その変数はfinal または事実上 final(一度しか代入されない)でなければなりません。
public void demo() {
int base = 10; // effectively final(再代入なし)
Function 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 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 化を推奨。