- JAVA
Java関数型インターフェース一覧|全43種の早見表と使い方
関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースで、ラムダ式やメソッド参照の「受け取り先の型」になるものです。 標準ライブラリの java.util.function パッケージには43種類が用意されていて、「引数がいくつで、何を返すか」で使うものが決まります。
この記事では、全43種を「引数・戻り値・抽象メソッド名・対応するラムダ・受け取る標準API」の一覧表にまとめ、引数と戻り値から逆引きできる表を付けました。43種すべてにラムダを代入して動かしたコードと出力、andThen・compose・negate・and・or の合成、型が合わないときのコンパイルエラー文、検査例外を投げたいときの自作 @FunctionalInterface まで載せています。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10(java・javac)。コンパイルエラーは javac -J-Duser.language=ja で日本語のメッセージを表示しています。
関数型インターフェースの早見表(基本の9種)
まず覚えたいのは次の9種です。残りの34種は、この9種の引数や戻り値を int・long・double・boolean に置き換えたものです。
| インターフェース | 引数 | 戻り値 | 抽象メソッド | 対応するラムダの例 | 受け取る標準APIの例 |
|---|---|---|---|---|---|
Function<T, R> |
T | R | apply |
s -> s.length() |
Stream.map |
BiFunction<T, U, R> |
T, U | R | apply |
(s, n) -> s.repeat(n) |
Map.compute |
UnaryOperator<T> |
T | T | apply |
s -> s.toUpperCase() |
List.replaceAll |
BinaryOperator<T> |
T, T | T | apply |
(a, b) -> a + b |
Stream.reduce |
Predicate<T> |
T | boolean | test |
s -> s.isEmpty() |
Stream.filter |
BiPredicate<T, U> |
T, U | boolean | test |
(s, n) -> s.length() > n |
Files.find |
Consumer<T> |
T | void | accept |
s -> System.out.println(s) |
Iterable.forEach |
BiConsumer<T, U> |
T, U | void | accept |
(k, v) -> System.out.println(k + "=" + v) |
Map.forEach |
Supplier<T> |
なし | T | get |
() -> "hello" |
Optional.orElseGet |
抽象メソッド名は「値を返すなら apply、判定なら test、受け取るだけなら accept、作るだけなら get」と覚えると、ラムダを変数に入れて呼び出すときに迷いません。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java関数型インターフェース一覧|全43種の早見表と使い方」
引数と戻り値から選ぶ逆引き表
「このラムダはどの型で受ければよいか」を、引数の数と戻り値から引ける表です。
参照型(ジェネリクス)だけで足りるとき
| 引数 | 何かを返す | 引数と同じ型を返す | boolean を返す | 何も返さない |
|---|---|---|---|---|
| 0個 | Supplier<T> |
— | BooleanSupplier |
Runnable(java.lang) |
| 1個 | Function<T, R> |
UnaryOperator<T> |
Predicate<T> |
Consumer<T> |
| 2個 | BiFunction<T, U, R> |
BinaryOperator<T> |
BiPredicate<T, U> |
BiConsumer<T, U> |
| 3個以上 | 自作する | 自作する | 自作する | 自作する |
引数0個で何も返さない形は java.lang の Runnable です(43種には含まれません)。引数が3個以上の形は標準に無いため、後半の方法で自作します。
int・long・double を箱に入れずに扱いたいとき
行が引数、列が戻り値です。
| 引数 \ 戻り値 | int | long | double | boolean | void | 参照型 R |
|---|---|---|---|---|---|---|
| なし | IntSupplier |
LongSupplier |
DoubleSupplier |
BooleanSupplier |
— | Supplier<T> |
| int | IntUnaryOperator |
IntToLongFunction |
IntToDoubleFunction |
IntPredicate |
IntConsumer |
IntFunction<R> |
| long | LongToIntFunction |
LongUnaryOperator |
LongToDoubleFunction |
LongPredicate |
LongConsumer |
LongFunction<R> |
| double | DoubleToIntFunction |
DoubleToLongFunction |
DoubleUnaryOperator |
DoublePredicate |
DoubleConsumer |
DoubleFunction<R> |
| T | ToIntFunction<T> |
ToLongFunction<T> |
ToDoubleFunction<T> |
Predicate<T> |
Consumer<T> |
Function<T, R> |
| T, U | ToIntBiFunction<T, U> |
ToLongBiFunction<T, U> |
ToDoubleBiFunction<T, U> |
BiPredicate<T, U> |
BiConsumer<T, U> |
BiFunction<T, U, R> |
同じ型2つを受けて同じ型を返す形と、参照型とプリミティブを1つずつ受ける形は次のとおりです。
| 引数 | 戻り値 | インターフェース |
|---|---|---|
| int, int | int | IntBinaryOperator |
| long, long | long | LongBinaryOperator |
| double, double | double | DoubleBinaryOperator |
| T, int | void | ObjIntConsumer<T> |
| T, long | void | ObjLongConsumer<T> |
| T, double | void | ObjDoubleConsumer<T> |
この2つの表に UnaryOperator と BinaryOperator を足すと、ちょうど43種になります。
名前の読み方
公式APIドキュメント(java.util.function のパッケージ説明)には、命名の決まりが書かれています。要点は次の4つです。
| 決まり | 例 |
|---|---|
| 基本の形は Function(T→R)・Consumer(T→void)・Predicate(T→boolean)・Supplier(引数なし→R) | Function<T, R> |
| 引数の数を変えるときは前に Bi を付ける | BiFunction<T, U, R> |
| 派生形として、Function を継承した UnaryOperator、BiFunction を継承した BinaryOperator がある | UnaryOperator<T> |
| 戻り値をプリミティブにするときは ToXxx を付け、引数は左から順に型名を付ける。Obj は「この引数は参照型のまま」の印 | ToIntFunction<T>、DoubleConsumer、ObjIntConsumer<T>、IntToDoubleFunction |
「To の後ろが戻り値、それ以外の型名は引数」 と読めば、名前から形がわかります。IntToDoubleFunction なら「int を受けて double を返す」です。
全43種の一覧表
int を扱う11種
| インターフェース | 引数 | 戻り値 | 抽象メソッド | 対応するラムダの例 | 受け取る標準APIの例 |
|---|---|---|---|---|---|
IntFunction<R> |
int | R | apply |
i -> "No." + i |
IntStream.mapToObj |
IntPredicate |
int | boolean | test |
i -> i % 2 == 0 |
IntStream.filter |
IntUnaryOperator |
int | int | applyAsInt |
i -> i * 2 |
IntStream.map |
IntBinaryOperator |
int, int | int | applyAsInt |
(a, b) -> a + b |
IntStream.reduce |
IntConsumer |
int | void | accept |
i -> System.out.println(i) |
IntStream.forEach |
IntSupplier |
なし | int | getAsInt |
() -> 42 |
IntStream.generate |
IntToLongFunction |
int | long | applyAsLong |
i -> (long) i * i |
IntStream.mapToLong |
IntToDoubleFunction |
int | double | applyAsDouble |
i -> i / 2.0 |
IntStream.mapToDouble |
ToIntFunction<T> |
T | int | applyAsInt |
s -> s.length() |
Stream.mapToInt |
ToIntBiFunction<T, U> |
T, U | int | applyAsInt |
(a, b) -> a.length() + b.length() |
ConcurrentHashMap.reduceToInt |
ObjIntConsumer<T> |
T, int | void | accept |
(s, i) -> System.out.println(s + i) |
IntStream.collect |
long を扱う11種
| インターフェース | 引数 | 戻り値 | 抽象メソッド | 対応するラムダの例 | 受け取る標準APIの例 |
|---|---|---|---|---|---|
LongFunction<R> |
long | R | apply |
n -> n + "件" |
LongStream.mapToObj |
LongPredicate |
long | boolean | test |
n -> n > 0 |
LongStream.filter |
LongUnaryOperator |
long | long | applyAsLong |
n -> n + 1 |
LongStream.map |
LongBinaryOperator |
long, long | long | applyAsLong |
(a, b) -> a * b |
LongStream.reduce |
LongConsumer |
long | void | accept |
n -> System.out.println(n) |
LongStream.forEach |
LongSupplier |
なし | long | getAsLong |
() -> 42L |
LongStream.generate |
LongToIntFunction |
long | int | applyAsInt |
n -> (int) (n % 10) |
LongStream.mapToInt |
LongToDoubleFunction |
long | double | applyAsDouble |
n -> n / 4.0 |
LongStream.mapToDouble |
ToLongFunction<T> |
T | long | applyAsLong |
s -> Long.parseLong(s) |
Stream.mapToLong |
ToLongBiFunction<T, U> |
T, U | long | applyAsLong |
(a, b) -> (long) a * b |
ConcurrentHashMap.reduceToLong |
ObjLongConsumer<T> |
T, long | void | accept |
(s, n) -> System.out.println(s + n) |
LongStream.collect |
double を扱う11種
| インターフェース | 引数 | 戻り値 | 抽象メソッド | 対応するラムダの例 | 受け取る標準APIの例 |
|---|---|---|---|---|---|
DoubleFunction<R> |
double | R | apply |
d -> String.format("%.1f", d) |
DoubleStream.mapToObj |
DoublePredicate |
double | boolean | test |
d -> d >= 0.5 |
DoubleStream.filter |
DoubleUnaryOperator |
double | double | applyAsDouble |
d -> d * 1.1 |
DoubleStream.map |
DoubleBinaryOperator |
double, double | double | applyAsDouble |
(a, b) -> Math.max(a, b) |
DoubleStream.reduce |
DoubleConsumer |
double | void | accept |
d -> System.out.println(d) |
DoubleStream.forEach |
DoubleSupplier |
なし | double | getAsDouble |
() -> 3.14 |
DoubleStream.generate |
DoubleToIntFunction |
double | int | applyAsInt |
d -> (int) Math.round(d) |
DoubleStream.mapToInt |
DoubleToLongFunction |
double | long | applyAsLong |
d -> (long) Math.floor(d) |
DoubleStream.mapToLong |
ToDoubleFunction<T> |
T | double | applyAsDouble |
s -> Double.parseDouble(s) |
Stream.mapToDouble |
ToDoubleBiFunction<T, U> |
T, U | double | applyAsDouble |
(a, b) -> (double) a / b |
ConcurrentHashMap.reduceToDouble |
ObjDoubleConsumer<T> |
T, double | void | accept |
(s, d) -> System.out.println(s + d) |
DoubleStream.collect |
boolean を返す1種
| インターフェース | 引数 | 戻り値 | 抽象メソッド | 対応するラムダの例 | 受け取る標準APIの例 |
|---|---|---|---|---|---|
BooleanSupplier |
なし | boolean | getAsBoolean |
() -> true |
— |
BooleanSupplier は、今回調べた java.util と java.util.stream の主なクラスには受け取るメソッドが見つかりませんでした。条件を後から評価したい自作メソッドの引数などで使います。
「受け取る標準APIの例」の列は、リフレクションで Stream・Optional・Map・Collectors などのメソッドの引数型を調べ、実際にその型を受け取るものを載せています。
43種すべてにラムダを代入して動かす
一覧表のラムダを、そのまま1つのファイルにまとめました。型宣言とラムダ、呼び出すメソッド名の組み合わせを確認する見本として使えます。
import java.util.function.*;
public class All43 {
public static void main(String[] args) {
// ---- 基本 ----
Function<String, Integer> f1 = s -> s.length();
System.out.println("Function: " + f1.apply("apple"));
BiFunction<String, Integer, String> f2 = (s, n) -> s.repeat(n);
System.out.println("BiFunction: " + f2.apply("ab", 3));
UnaryOperator<String> f3 = s -> s.toUpperCase();
System.out.println("UnaryOperator: " + f3.apply("apple"));
BinaryOperator<Integer> f4 = (a, b) -> a + b;
System.out.println("BinaryOperator: " + f4.apply(2, 3));
Predicate<String> f5 = s -> s.isEmpty();
System.out.println("Predicate: " + f5.test(""));
BiPredicate<String, Integer> f6 = (s, n) -> s.length() > n;
System.out.println("BiPredicate: " + f6.test("apple", 3));
Consumer<String> f7 = s -> System.out.println("Consumer: " + s);
f7.accept("apple");
BiConsumer<String, Integer> f8 = (k, v) -> System.out.println("BiConsumer: " + k + "=" + v);
f8.accept("apple", 3);
Supplier<String> f9 = () -> "hello";
System.out.println("Supplier: " + f9.get());
// ---- int ----
IntFunction<String> f10 = i -> "No." + i;
System.out.println("IntFunction: " + f10.apply(7));
IntPredicate f11 = i -> i % 2 == 0;
System.out.println("IntPredicate: " + f11.test(7));
IntUnaryOperator f12 = i -> i * 2;
System.out.println("IntUnaryOperator: " + f12.applyAsInt(7));
IntBinaryOperator f13 = (a, b) -> a + b;
System.out.println("IntBinaryOperator: " + f13.applyAsInt(2, 3));
IntConsumer f14 = i -> System.out.println("IntConsumer: " + i);
f14.accept(7);
IntSupplier f15 = () -> 42;
System.out.println("IntSupplier: " + f15.getAsInt());
IntToLongFunction f16 = i -> (long) i * i;
System.out.println("IntToLongFunction: " + f16.applyAsLong(100000));
IntToDoubleFunction f17 = i -> i / 2.0;
System.out.println("IntToDoubleFunction: " + f17.applyAsDouble(7));
ToIntFunction<String> f18 = s -> s.length();
System.out.println("ToIntFunction: " + f18.applyAsInt("apple"));
ToIntBiFunction<String, String> f19 = (a, b) -> a.length() + b.length();
System.out.println("ToIntBiFunction: " + f19.applyAsInt("ab", "cde"));
ObjIntConsumer<String> f20 = (s, i) -> System.out.println("ObjIntConsumer: " + s + i);
f20.accept("No.", 7);
// ---- long ----
LongFunction<String> f21 = n -> n + "件";
System.out.println("LongFunction: " + f21.apply(7L));
LongPredicate f22 = n -> n > 0;
System.out.println("LongPredicate: " + f22.test(7L));
LongUnaryOperator f23 = n -> n + 1;
System.out.println("LongUnaryOperator: " + f23.applyAsLong(7L));
LongBinaryOperator f24 = (a, b) -> a * b;
System.out.println("LongBinaryOperator: " + f24.applyAsLong(2L, 3L));
LongConsumer f25 = n -> System.out.println("LongConsumer: " + n);
f25.accept(7L);
LongSupplier f26 = () -> 42L;
System.out.println("LongSupplier: " + f26.getAsLong());
LongToIntFunction f27 = n -> (int) (n % 10);
System.out.println("LongToIntFunction: " + f27.applyAsInt(1234L));
LongToDoubleFunction f28 = n -> n / 4.0;
System.out.println("LongToDoubleFunction: " + f28.applyAsDouble(7L));
ToLongFunction<String> f29 = s -> Long.parseLong(s);
System.out.println("ToLongFunction: " + f29.applyAsLong("123"));
ToLongBiFunction<Integer, Integer> f30 = (a, b) -> (long) a * b;
System.out.println("ToLongBiFunction: " + f30.applyAsLong(100000, 100000));
ObjLongConsumer<String> f31 = (s, n) -> System.out.println("ObjLongConsumer: " + s + n);
f31.accept("id=", 7L);
// ---- double ----
DoubleFunction<String> f32 = d -> String.format("%.1f", d);
System.out.println("DoubleFunction: " + f32.apply(3.14159));
DoublePredicate f33 = d -> d >= 0.5;
System.out.println("DoublePredicate: " + f33.test(0.3));
DoubleUnaryOperator f34 = d -> d * 1.1;
System.out.println("DoubleUnaryOperator: " + f34.applyAsDouble(100.0));
DoubleBinaryOperator f35 = (a, b) -> Math.max(a, b);
System.out.println("DoubleBinaryOperator: " + f35.applyAsDouble(1.5, 2.5));
DoubleConsumer f36 = d -> System.out.println("DoubleConsumer: " + d);
f36.accept(1.5);
DoubleSupplier f37 = () -> 3.14;
System.out.println("DoubleSupplier: " + f37.getAsDouble());
DoubleToIntFunction f38 = d -> (int) Math.round(d);
System.out.println("DoubleToIntFunction: " + f38.applyAsInt(2.6));
DoubleToLongFunction f39 = d -> (long) Math.floor(d);
System.out.println("DoubleToLongFunction: " + f39.applyAsLong(2.6));
ToDoubleFunction<String> f40 = s -> Double.parseDouble(s);
System.out.println("ToDoubleFunction: " + f40.applyAsDouble("1.5"));
ToDoubleBiFunction<Integer, Integer> f41 = (a, b) -> (double) a / b;
System.out.println("ToDoubleBiFunction: " + f41.applyAsDouble(1, 4));
ObjDoubleConsumer<String> f42 = (s, d) -> System.out.println("ObjDoubleConsumer: " + s + d);
f42.accept("rate=", 0.5);
// ---- boolean ----
BooleanSupplier f43 = () -> true;
System.out.println("BooleanSupplier: " + f43.getAsBoolean());
}
}
java All43.java
実行結果:
Function: 5
BiFunction: ababab
UnaryOperator: APPLE
BinaryOperator: 5
Predicate: true
BiPredicate: true
Consumer: apple
BiConsumer: apple=3
Supplier: hello
IntFunction: No.7
IntPredicate: false
IntUnaryOperator: 14
IntBinaryOperator: 5
IntConsumer: 7
IntSupplier: 42
IntToLongFunction: 10000000000
IntToDoubleFunction: 3.5
ToIntFunction: 5
ToIntBiFunction: 5
ObjIntConsumer: No.7
LongFunction: 7件
LongPredicate: true
LongUnaryOperator: 8
LongBinaryOperator: 6
LongConsumer: 7
LongSupplier: 42
LongToIntFunction: 4
LongToDoubleFunction: 1.75
ToLongFunction: 123
ToLongBiFunction: 10000000000
ObjLongConsumer: id=7
DoubleFunction: 3.1
DoublePredicate: false
DoubleUnaryOperator: 110.00000000000001
DoubleBinaryOperator: 2.5
DoubleConsumer: 1.5
DoubleSupplier: 3.14
DoubleToIntFunction: 3
DoubleToLongFunction: 2
ToDoubleFunction: 1.5
ToDoubleBiFunction: 0.25
ObjDoubleConsumer: rate=0.5
BooleanSupplier: true
Consumer 系は戻り値が無いので、ラムダの中で出力しています。数が43であることは、公式APIドキュメントのパッケージ一覧でも確認しました。
andThen・compose・negate・and・or で合成する
関数型インターフェースには、2つの関数をつないで新しい関数を作る default メソッドがあります。どのインターフェースにどのメソッドがあるかを、リフレクションで調べた結果が次の表です。
| メソッド | 持っているインターフェース | 働き |
|---|---|---|
andThen |
Function、BiFunction、Consumer、BiConsumer、Int/Long/DoubleConsumer、Int/Long/DoubleUnaryOperator(UnaryOperator と BinaryOperator は継承で使える) | 自分を実行した後に、引数の関数を実行する |
compose |
Function、Int/Long/DoubleUnaryOperator | 引数の関数を先に実行し、その結果を自分に渡す |
identity(static) |
Function、UnaryOperator、Int/Long/DoubleUnaryOperator | 受け取った値をそのまま返す関数を作る |
negate / and / or |
Predicate、BiPredicate、Int/Long/DoublePredicate | 判定の反転、かつ、または |
not / isEqual(static) |
Predicate | メソッド参照を反転する、等しいかを判定する |
minBy / maxBy(static) |
BinaryOperator | Comparator で小さいほう・大きいほうを返す |
Supplier 系、ToIntFunction などの ToXxx 系、IntFunction などの XxxFunction 系、XxxBinaryOperator、ObjXxxConsumer には合成用のメソッドはありません。
import java.util.List;
import java.util.function.*;
public class Compose {
public static void main(String[] args) {
// ---- Function: andThen と compose ----
Function<Integer, Integer> plus10 = x -> x + 10;
Function<Integer, Integer> times2 = x -> x * 2;
System.out.println("plus10.andThen(times2).apply(5) = " + plus10.andThen(times2).apply(5));
System.out.println("plus10.compose(times2).apply(5) = " + plus10.compose(times2).apply(5));
// 型が変わっていく合成
Function<String, String> trim = String::trim;
Function<String, Integer> length = String::length;
Function<Integer, String> stars = "*"::repeat;
System.out.println("trim→length→stars: " + trim.andThen(length).andThen(stars).apply(" apple "));
// ---- Predicate: negate / and / or / not / isEqual ----
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isLong = s -> s.length() >= 5;
Predicate<String> startsWithA = s -> s.startsWith("a");
List<String> words = List.of("apple", "ant", "", "banana", "kiwi");
System.out.println("negate : " + words.stream().filter(isEmpty.negate()).toList());
System.out.println("Predicate.not : " + words.stream().filter(Predicate.not(String::isEmpty)).toList());
System.out.println("and : " + words.stream().filter(isLong.and(startsWithA)).toList());
System.out.println("or : " + words.stream().filter(isLong.or(startsWithA)).toList());
System.out.println("a.or(b).and(c) : " + words.stream().filter(isEmpty.or(isLong).and(startsWithA)).toList());
System.out.println("a.or(b.and(c)) : " + words.stream().filter(isEmpty.or(isLong.and(startsWithA))).toList());
System.out.println("isEqual(\"kiwi\") : " + words.stream().filter(Predicate.isEqual("kiwi")).toList());
// and は左が false なら右を呼ばない
Predicate<String> left = s -> { System.out.print("[左] "); return false; };
Predicate<String> right = s -> { System.out.print("[右] "); return true; };
System.out.println("left.and(right) = " + left.and(right).test("x"));
System.out.println("left.or(right) = " + left.or(right).test("x"));
// ---- Consumer: andThen は同じ値を順に渡す ----
Consumer<String> first = s -> System.out.println("1番目: " + s);
Consumer<String> second = s -> System.out.println("2番目: " + s.toUpperCase());
first.andThen(second).accept("apple");
// ---- BiFunction.andThen:結果を Function に渡す ----
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
Function<Integer, String> label = n -> "面積=" + n;
System.out.println(multiply.andThen(label).apply(3, 4));
}
}
実行結果:
plus10.andThen(times2).apply(5) = 30
plus10.compose(times2).apply(5) = 20
trim→length→stars: *****
negate : [apple, ant, banana, kiwi]
Predicate.not : [apple, ant, banana, kiwi]
and : [apple]
or : [apple, ant, banana]
a.or(b).and(c) : [apple]
a.or(b.and(c)) : [apple, ]
isEqual("kiwi") : [kiwi]
[左] left.and(right) = false
[左] [右] left.or(right) = true
1番目: apple
2番目: APPLE
面積=12
andThen と compose は実行順が逆
plus10.andThen(times2) は「10を足してから2倍」で (5+10)×2=30、plus10.compose(times2) は「2倍してから10を足す」で 5×2+10=20 です。左から右へ読める andThen を基本にすると、処理の順番とコードの並びがそろいます。
and と or は左から順につながる
isEmpty.or(isLong).and(startsWithA) は、メソッド呼び出しの順に「(空 または 5文字以上) かつ a で始まる」と評価され、結果は [apple] でした。isEmpty.or(isLong.and(startsWithA)) と書くと「空 または (5文字以上 かつ a で始まる)」になり、空文字も残ります。&& と || の優先順位とは違い、書いた順にまとまる点を押さえておくと、条件の組み立てで迷いません。
また、left.and(right) では左が false のとき右は呼ばれず、left.or(right) では左が false なので右も呼ばれています。&& や || と同じ短絡評価です。
Function の andThen は前の結果を次に渡しますが、Consumer の andThen は同じ引数を2つの処理に順番に渡します。 出力でも、1番目と2番目の両方に apple が渡っています。
型が合わないときのコンパイルエラーと直し方
よく出会うエラーを、実際のメッセージつきで並べます。
形が同じでも、別のインターフェースには代入できない
import java.util.function.*;
public class E1 {
public static void main(String[] args) {
Function<String, Boolean> isEmpty = s -> s.isEmpty();
Predicate<String> p = isEmpty;
}
}
E1.java:6: エラー: 不適合な型: Function<String,Boolean>をPredicate<String>に変換できません:
Predicate<String> p = isEmpty;
^
エラー1個
Function<String, Boolean> と Predicate<String> は、どちらも「String を受けて真偽を返す」形ですが、型としては別物です。BiFunction を BinaryOperator に、Function を UnaryOperator に代入した場合も、同じ「不適合な型: …に変換できません」が出ました。
直し方は、メソッド参照 変数::抽象メソッド名 でつなぎ直すことです。 逆向き(BinaryOperator から BiFunction へ)は継承関係があるため、そのまま代入できます。
import java.util.function.*;
import java.util.stream.*;
public class Fix {
public static void main(String[] args) {
// 形が同じでも別の型。メソッド参照でつなぎ直す
Function<String, Boolean> isEmpty = s -> s.isEmpty();
Predicate<String> p = isEmpty::apply;
System.out.println("Predicate に変換: " + p.test(""));
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
BinaryOperator<Integer> op = add::apply;
System.out.println("BinaryOperator に変換: " + op.apply(2, 3));
// 逆向き(BinaryOperator → BiFunction)はそのまま代入できる
BiFunction<Integer, Integer, Integer> back = op;
System.out.println("BiFunction に代入: " + back.apply(4, 5));
// IntStream.map には IntUnaryOperator を渡す
IntUnaryOperator twice = x -> x * 2;
System.out.println(IntStream.range(0, 3).map(twice).boxed().toList());
// var を使うときはキャストで型を示す
var len = (Function<String, Integer>) s -> s.length();
System.out.println("var + キャスト: " + len.apply("apple"));
}
}
Predicate に変換: true
BinaryOperator に変換: 5
BiFunction に代入: 9
[0, 2, 4]
var + キャスト: 5
プリミティブ用のAPIに Function を渡した
import java.util.function.*;
import java.util.stream.*;
public class E7 {
public static void main(String[] args) {
Function<Integer, Integer> twice = x -> x * 2;
IntStream.range(0, 3).map(twice).forEach(System.out::println);
}
}
E7.java:7: エラー: 不適合な型: Function<Integer,Integer>をIntUnaryOperatorに変換できません:
IntStream.range(0, 3).map(twice).forEach(System.out::println);
^
ノート: 一部のメッセージは簡略化されています。-Xdiags:verboseで再コンパイルして完全な出力を取得してください
エラー1個
IntStream の map が受け取るのは IntUnaryOperator です。変数の型を IntUnaryOperator にすると通ります(上の Fix.java の4つ目)。ラムダを直接 map(x -> x * 2) と書いた場合は、コンパイラが IntUnaryOperator として扱うので、型を意識せずに書けます。
そのほかのエラー文
| 書いたコード | エラー文 | 直し方 |
|---|---|---|
Function<String, Integer> f = s -> s.toUpperCase(); |
不適合な型: ラムダ式の戻り型が不正です StringをIntegerに変換できません | 型引数の最後が戻り値の型。Function<String, String> か UnaryOperator にする |
Predicate<String> p = s -> System.out.println(s); |
不適合な型: ラムダ式の戻り型が不正です voidをbooleanに変換できません | 出力するだけなら Consumer で受ける |
Consumer<String> c = s -> { return s.length(); }; |
不適合な型: ラムダ式の戻り型が不正です 予期しない戻り値 | 値を返すなら Function や ToIntFunction で受ける |
Supplier<String> s = x -> "hello"; |
不適合な型: ラムダ式のパラメータ型は不適合です | Supplier は引数0個。() -> "hello" と書く |
Function<int, int> f = x -> x * 2; |
予期しない型 期待値: 参照 検出値: int | 型引数にプリミティブは書けない。IntUnaryOperator を使う |
Object o = () -> System.out.println("hello"); |
不適合な型: Objectは機能インタフェースではありません | 受け取り先を Runnable などの関数型インターフェースにする |
var v = (String s) -> s.length(); |
ローカル変数vの型を推論できません(ラムダ式には明示的なターゲット型が必要です) | 型を宣言するか、キャストで型を示す |
javac のメッセージでは、関数型インターフェースが「機能インタフェース」と表記されています。
検査例外を投げたいときは自作する
標準の43種の抽象メソッドには throws が付いていません。そのため、IOException のような検査例外を投げるメソッドをラムダの中で呼ぶと、コンパイルエラーになります。
import java.nio.file.*;
import java.util.function.*;
public class E8 {
public static void main(String[] args) {
Function<Path, String> read = p -> Files.readString(p);
}
}
E8.java:6: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
Function<Path, String> read = p -> Files.readString(p);
^
エラー1個
対応は2つあります。ラムダの中で try-catch を書くか、throws 付きの関数型インターフェースを自作するかです。後者の例を示します。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
public class Throwing {
// 検査例外を投げられる Function を自作する
@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T value) throws E;
// 標準の Function に変換する(検査例外は非検査例外で包む)
static <T, R> Function<T, R> unchecked(ThrowingFunction<T, R, ? extends IOException> f) {
return value -> {
try {
return f.apply(value);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
}
public static void main(String[] args) throws IOException {
Path dir = Files.createTempDirectory("fi");
Path a = Files.writeString(dir.resolve("a.txt"), "りんご");
Path b = Files.writeString(dir.resolve("b.txt"), "みかん");
Path none = dir.resolve("none.txt");
// 1. 自作インターフェースなら、ラムダの中で IOException をそのまま投げられる
ThrowingFunction<Path, String, IOException> read = p -> Files.readString(p);
System.out.println("1. " + read.apply(a));
// 2. Stream.map に渡すときは unchecked で Function に変換する
List<String> texts = List.of(a, b).stream()
.map(ThrowingFunction.unchecked(Files::readString))
.toList();
System.out.println("2. " + texts);
// 3. ファイルが無いときは UncheckedIOException として届く
try {
List.of(a, none).stream().map(ThrowingFunction.unchecked(Files::readString)).toList();
} catch (UncheckedIOException e) {
System.out.println("3. " + e.getClass().getSimpleName() + " / 原因: " + e.getCause().getClass().getSimpleName());
}
Files.delete(a); Files.delete(b); Files.delete(dir);
}
}
実行結果:
1. りんご
2. [りんご, みかん]
3. UncheckedIOException / 原因: NoSuchFileException
ポイントは3つです。
- 抽象メソッドに
throws Eを付けると、ラムダの中で検査例外をそのまま投げられます。例外の型も型引数にしておくと、IOException 以外にも使い回せます。 - Stream.map など標準のAPIは Function を受け取るので、渡すときは変換用の static メソッド(ここでは unchecked)で Function に包みます。検査例外は UncheckedIOException に包んで外へ出しています。
- インターフェースの中には、抽象メソッドのほかに static メソッドや default メソッドを置けます。抽象メソッドが1つであれば、関数型インターフェースのままです。
@FunctionalInterface を付けると、抽象メソッドの数を確かめてもらえる
@FunctionalInterface は付けなくてもラムダを代入できますが、付けておくと、抽象メソッドが2つになったときにコンパイルエラーで知らせてくれます。
@FunctionalInterface
interface Converter<T, R> {
R convert(T value);
R convertOrDefault(T value, R defaultValue);
}
E9.java:1: エラー: 予期しない@FunctionalInterface注釈
@FunctionalInterface
^
Converterは機能インタフェースではありません
インタフェース Converterで複数のオーバーライドしない抽象メソッドが見つかりました
エラー1個
後から誰かがメソッドを足したときにも、ラムダを渡している側が動かなくなる前に気づけます。自作するときは必ず付けておくのがおすすめです。
自作するか、標準を使うかの判断表
| 状況 | 選ぶもの | 理由 |
|---|---|---|
| 引数が0〜2個で、検査例外を投げない | 標準の43種 | 合成メソッドが使え、標準APIにそのまま渡せる |
| int・long・double を大量に扱う | プリミティブ特殊化 | Integer などへの箱詰めを挟まずに済む |
| 引数が3個以上 | 自作 | 標準に無い形 |
| 検査例外を投げたい | 自作(throws 付き) | 標準の抽象メソッドには throws が無い |
よくある質問
Q. Function と UnaryOperator はどちらを使えばよいですか? A. 引数と戻り値が同じ型なら UnaryOperator が短く書けます。List.replaceAll のように UnaryOperator を受け取るAPIもあります。Function 型の変数は UnaryOperator 型の変数に代入できないので、受け取る側の型に合わせて宣言します。
Q. プリミティブ特殊化はいつ使いますか? A. IntStream・LongStream・DoubleStream のメソッドは引数がプリミティブ特殊化なので、自然に使うことになります。自作メソッドでは、int などを大量に処理する箇所で選びます。
Q. ラムダを var で受けられますか? A. そのままでは「ローカル変数vの型を推論できません」になります。Fix.java の最後のように、キャストで型を示せば受けられます。
まとめ
- java.util.function には43種あり、基本は9種。 残り34種は int・long・double・boolean 向けの特殊化。
- 選び方は「引数の数」と「戻り値」。 返すなら Function、判定なら Predicate、受け取るだけなら Consumer、作るだけなら Supplier。
- 抽象メソッド名は apply・test・accept・get。 プリミティブを返すものは applyAsInt、getAsLong のように As+型名が付く。
- 名前は「To の後ろが戻り値、それ以外は引数」と読む。
- andThen は左から右、compose はその逆。 and・or は書いた順にまとまる。
- 形が同じでも別のインターフェースには代入できない。
変数::applyのようにメソッド参照でつなぎ直す。 - 検査例外を投げたいとき・引数が3個以上のときは、@FunctionalInterface を付けて自作する。
参考・出典
- Java SE 21 API ドキュメント「Package java.util.function」 https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/package-summary.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java関数型インターフェース一覧|全43種の早見表と使い方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。