SJ blog
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を組み合わせることで、コードが劇的に簡潔になります。