関数型インターフェースとは、抽象メソッドを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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?