Predicate / Function / Consumer / Supplier
| 対応: | Java 8(2014) |
|---|
Java 8で追加された主要な関数型インターフェースです。ラムダ式やメソッド参照と組み合わせて使います。Javaの関数型インターフェースは「引数と戻り値の有無」で4種に分かれます。引数なし→Supplier、引数あり・戻り値あり→Function、引数あり・戻り値なし→Consumer、引数あり・真偽を返す→Predicate です。それぞれ「判定・変換・消費・生成」という役割を持ちます。
構文
// Predicate<T>: T を受け取り boolean を返します
Predicate<T> pred = (T t) -> /* boolean式 */;
boolean result = pred.test(t);
// Function<T, R>: T を受け取り R を返します
Function<T, R> func = (T t) -> /* R型の値 */;
R result = func.apply(t);
// Consumer<T>: T を受け取り何も返しません
Consumer<T> cons = (T t) -> { /* 処理 */ };
cons.accept(t);
// Supplier<T>: 引数なしで T を返します
Supplier<T> supp = () -> /* T型の値 */;
T value = supp.get();
// BiFunction<T, U, R>: 2引数を受け取り R を返します
BiFunction<T, U, R> bif = (T t, U u) -> /* R型の値 */;
// UnaryOperator<T>: T を受け取り同じ型 T を返します
UnaryOperator<T> op = (T t) -> /* T型の値 */;
主な関数型インターフェース一覧
| インターフェース | 抽象メソッド | 概要 |
|---|---|---|
| Predicate<T> | test(T t) → boolean | 条件判定に使います。ストリームの filter() などに渡します。 |
| Function<T, R> | apply(T t) → R | 値を変換します。ストリームの map() などに渡します。 |
| Consumer<T> | accept(T t) → void | 値を受け取って処理し、何も返しません。forEach() などに渡します。 |
| Supplier<T> | get() → T | 引数なしで値を生成・返します。遅延評価に使います。 |
| BiFunction<T, U, R> | apply(T t, U u) → R | 2つの引数を受け取り値を返します。 |
| BiConsumer<T, U> | accept(T t, U u) → void | 2引数を受け取り処理します。Map.forEach() などに渡します。 |
| UnaryOperator<T> | apply(T t) → T | 同じ型の変換を行います。Function<T,T> の特化型です。 |
| BinaryOperator<T> | apply(T t, T u) → T | 同じ型の2引数を受け取り同じ型を返します。reduce() などに渡します。 |
サンプルコード
FunctionalInterfaces.java
import java.util.*;
import java.util.function.*;
class FunctionalInterfaces {
public static void main(String[] args) {
// Predicate: 偶数かどうかを判定します
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // 『true』と出力されます
System.out.println(isEven.test(7)); // 『false』と出力されます
// Predicateの合成: and() / or() / negate() で組み合わせます
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println(isEvenAndPositive.test(4)); // 『true』と出力されます
System.out.println(isEvenAndPositive.test(-2)); // 『false』と出力されます
// Function: 文字列を大文字に変換します
Function<String, String> toUpper = String::toUpperCase;
System.out.println(toUpper.apply("hello")); // 『HELLO』と出力されます
// Functionの合成: andThen() で連結します
Function<String, Integer> toLength = String::length;
Function<String, Integer> upperLength = toUpper.andThen(toLength);
System.out.println(upperLength.apply("hello")); // 『5』と出力されます
// Consumer: リストの各要素を出力します
Consumer<String> printer = s -> System.out.println("> " + s);
List.of("A", "B", "C").forEach(printer);
// Supplier: 遅延評価でデフォルト値を生成します
Supplier<List<String>> listFactory = ArrayList::new;
List<String> newList = listFactory.get();
newList.add("item");
System.out.println(newList); // 『[item]』と出力されます
}
}
コンパイルして実行すると次のようになります。
javac FunctionalInterfaces.java java FunctionalInterfaces true false true false HELLO 5 > A > B > C [item]
よくあるミス
よくあるミス1: ラムダ式内でチェック例外を投げてコンパイルエラー
Runnable や Consumer などの標準的な関数型インターフェースはチェック例外を宣言していないため、ラムダ式内でIOExceptionなどを直接スローするとコンパイルエラーになります。
FunctionalInterfacesNg1.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
class FunctionalInterfacesNg1 {
public static void main(String[] args) {
Consumer<String> writer = filename -> {
Files.writeString(Path.of(filename), "五条悟の術式メモ");
};
writer.accept("memo.txt");
}
}
コンパイルして実行すると次のようになります。
javac FunctionalInterfacesNg1.java
java FunctionalInterfacesNg1
FunctionalInterfacesNg1.java:9: error: unreported exception IOException; must be caught or declared to be thrown
Files.writeString(Path.of(filename), "五条悟の術式メモ");
^
ラムダ式内でtry-catchしてチェック例外を処理します。または、チェック例外を宣言したカスタム関数型インターフェースを定義します。
FunctionalInterfacesOk1.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
class FunctionalInterfacesOk1 {
public static void main(String[] args) {
Consumer<String> writer = filename -> {
try {
Files.writeString(Path.of(filename), "五条悟の術式メモ");
System.out.println("書き込み完了: " + filename);
} catch (IOException e) {
System.err.println("書き込みエラー: " + e.getMessage());
}
};
writer.accept("memo.txt");
}
}
コンパイルして実行すると次のようになります。
javac FunctionalInterfacesOk1.java java FunctionalInterfacesOk1 書き込み完了: memo.txt
よくあるミス2: Consumer<T>とFunction<T,R>を混同してコンパイルエラー
Consumer<T>は戻り値なし(void)のインターフェースです。値を返すラムダ式をConsumer<T>として扱おうとするとコンパイルエラーになります。値を返す場合はFunction<T,R>を使います。
FunctionalInterfacesNg2.java
import java.util.function.Consumer;
class FunctionalInterfacesNg2 {
public static void main(String[] args) {
Consumer<String> getName = name -> "術師: " + name;
System.out.println(getName.accept("虎杖悠仁"));
}
}
コンパイルして実行すると次のようになります。
javac FunctionalInterfacesNg2.java
java FunctionalInterfacesNg2
FunctionalInterfacesNg2.java:5: error: incompatible types: bad return type in lambda expression
Consumer<String> getName = name -> "術師: " + name;
^
戻り値がある場合はFunction<T,R>を使います。Consumer<T>はaccept()がvoidを返すため、式の結果を返すラムダ式は使えません。
FunctionalInterfacesOk2.java
import java.util.function.Function;
import java.util.function.Consumer;
class FunctionalInterfacesOk2 {
public static void main(String[] args) {
Function<String, String> getName = name -> "術師: " + name;
System.out.println(getName.apply("虎杖悠仁"));
Consumer<String> printer = name -> System.out.println("術師: " + name);
printer.accept("釘崎野薔薇");
}
}
コンパイルして実行すると次のようになります。
javac FunctionalInterfacesOk2.java java FunctionalInterfacesOk2 術師: 虎杖悠仁 術師: 釘崎野薔薇
概要
これらのインターフェースは java.util.function パッケージに含まれており、ラムダ式・メソッド参照の型として使われます。ストリームAPIや Optional でも広く使われているため、使い方を覚えておくと非常に便利です。
Predicate は and() / or() / negate()、Function は andThen() / compose() でメソッドを合成できます。プリミティブ型専用の IntPredicate・IntFunction<R>・IntConsumer・IntSupplier などを使うとオートボクシングのコストを避けられます。
ラムダ式の書き方については『ラムダ式 / (x) -> x * 2』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。