Java
Z
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
JavaのLambda式入門 — 匿名クラスとの比較から関数型インターフェースまで
Java 8で導入されたLambda式の構文、関数型インターフェース(Function/Predicate/Consumer/Supplier)、メソッド参照を実例で解説します。
一言結論
Lambda式は匿名クラスの省略記法ではなく関数型インターフェースを値として扱うパラダイムシフトであり、Function/Predicate/Consumerを使いこなせばコレクション処理が宣言的に書けるようになる。
Lambda式とは
匿名クラスを簡潔に書くための構文です。特に「1メソッドだけのインターフェース(関数型インターフェース)」の実装を短く書けます。
// 匿名クラス(従来の書き方)
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
};
// Lambda式(同じ意味)
Runnable r = () -> System.out.println("Hello!");
Lambda式の構文
// 引数なし
() -> System.out.println("Hello")
// 引数1つ(型は省略可能)
name -> System.out.println(name)
(String name) -> System.out.println(name)
// 引数複数
(a, b) -> a + b
// 複数行の処理はブロック + return
(a, b) -> {
int result = a + b;
return result;
}
関数型インターフェース
@FunctionalInterface アノテーションが付いた、抽象メソッドが1つだけのインターフェースです。
Function<T, R>: T を受け取り R を返す
Function<String, Integer> strLen = s -> s.length();
System.out.println(strLen.apply("hello")); // 5
// andThen: 関数を合成
Function<String, String> toUpper = s -> s.toUpperCase();
Function<String, String> addBang = s -> s + "!";
Function<String, String> shout = toUpper.andThen(addBang);
System.out.println(shout.apply("hello")); // HELLO!
Predicate: T を受け取り boolean を返す
Predicate<String> isEmpty = s -> s.isEmpty();
Predicate<String> isNotEmpty = isEmpty.negate(); // 否定
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
// and / or で組み合わせ
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
List<Integer> result = numbers.stream()
.filter(isEvenAndPositive)
.collect(Collectors.toList());
Consumer: T を受け取り何も返さない
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> logger = s -> log.info(s);
// andThen で複数のConsumerを組み合わせ
Consumer<String> printAndLog = printer.andThen(logger);
printAndLog.accept("hello");
// forEachと組み合わせ
names.forEach(name -> System.out.println("Name: " + name));
Supplier: 何も受け取らず T を返す
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();
// Optionalと組み合わせ
String result = Optional.ofNullable(value)
.orElseGet(() -> generateDefault());
BiFunction / BiPredicate / BiConsumer
引数が2つのバージョンです。
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
System.out.println(repeat.apply("ab", 3)); // ababab
BiPredicate<String, String> startsWith = (s, prefix) -> s.startsWith(prefix);
メソッド参照
既存のメソッドをLambda式の代わりに直接参照できます。
// 静的メソッド参照
Function<String, Integer> parser = Integer::parseInt;
// s -> Integer.parseInt(s) と同じ
// インスタンスメソッド参照(特定のオブジェクト)
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
// s -> prefix.concat(s) と同じ
// インスタンスメソッド参照(任意のオブジェクト)
Function<String, String> toUpper = String::toUpperCase;
// s -> s.toUpperCase() と同じ
// コンストラクタ参照
Supplier<ArrayList<String>> listFactory = ArrayList::new;
// () -> new ArrayList<>() と同じ
実際によく使う場面:
names.stream()
.map(String::toUpperCase) // s -> s.toUpperCase()
.filter(Objects::nonNull) // s -> s != null
.sorted(Comparator.naturalOrder())
.forEach(System.out::println); // s -> System.out.println(s)
変数のキャプチャ
Lambda式の外側の変数を使う場合、その変数は effectively final(実質的にfinal)である必要があります。
String prefix = "Hello, ";
// prefix = "Hi, "; // ← これがあるとコンパイルエラー
names.forEach(name -> System.out.println(prefix + name));
カスタム関数型インターフェース
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
TriFunction<Integer, Integer, Integer, Integer> sum3 = (a, b, c) -> a + b + c;
System.out.println(sum3.apply(1, 2, 3)); // 6
まとめ
| インターフェース | シグネチャ | 用途 |
|---|---|---|
Function<T,R> | T → R | 変換・変換 |
Predicate<T> | T → boolean | フィルタ・条件 |
Consumer<T> | T → void | 副作用(出力・保存) |
Supplier<T> | () → T | 生成・遅延評価 |
UnaryOperator<T> | T → T | 同じ型での変換 |
BinaryOperator<T> | (T, T) → T | 集約 |
Lambda式とStream APIを組み合わせることで、コードが劇的に簡潔になります。