3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaGold 主要な関数型インターフェースと使い方

3
Last updated at Posted at 2024-11-07

Java Goldでは、ラムダ式、メソッド参照、Stream APIとあわせて、java.util.function パッケージの関数型インターフェースが頻出します。

単に名前を暗記するよりも、次の3点で整理すると理解しやすくなります。

  • 引数を取るか
  • 戻り値を返すか
  • 戻り値の型が同じか、別の型か

この記事では、Java Goldで重要な関数型インターフェースを、公式APIの内容をもとに整理します。


まず結論:型の形で覚える

最初に、以下の対応を押さえるのが重要です。

インターフェース ラムダ式の形 意味
Supplier<T> <mark>() -> T</mark> 値を供給する
Consumer<T> <mark>T -> void</mark> 値を受け取って処理する
Predicate<T> <mark>T -> boolean</mark> 条件判定する
Function<T, R> <mark>T -> R</mark> 値を変換する
UnaryOperator<T> <mark>T -> T</mark> 同じ型で変換する
BiConsumer<T, U> <mark>(T, U) -> void</mark> 2つの値を受け取って処理する
BiPredicate<T, U> <mark>(T, U) -> boolean</mark> 2つの値で条件判定する
BiFunction<T, U, R> <mark>(T, U) -> R</mark> 2つの値から1つの結果を返す
BinaryOperator<T> <mark>(T, T) -> T</mark> 同じ型2つから同じ型を返す
Runnable <mark>() -> void</mark> 処理だけ実行する
Callable<V> <mark>() -> V</mark> 結果を返すタスク

関数型インターフェースとは

関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースです。

ラムダ式やメソッド参照は、この関数型インターフェースの実装として扱われます。

@FunctionalInterface
interface Greeting {
    void hello(String name);
}
Greeting greeting = name -> System.out.println("Hello, " + name);
greeting.hello("Alice");

重要ポイント

@FunctionalInterface は必須ではありません。

ただし、付けておくと抽象メソッドが2つ以上になった場合にコンパイルエラーになるため、安全です。

@FunctionalInterface
interface Sample {
    void execute();

    // defaultメソッドは抽象メソッドに数えない
    default void log() {
        System.out.println("log");
    }

    // staticメソッドも抽象メソッドに数えない
    static void info() {
        System.out.println("info");
    }
}

Java Goldポイント

default メソッドや static メソッドがあっても、抽象メソッドが1つなら関数型インターフェースです。


主要な関数型インターフェース一覧

パッケージ インターフェース 抽象メソッド Java Gold重要度
java.util.function Supplier<T> T get() () -> T
java.util.function Consumer<T> void accept(T) T -> void
java.util.function BiConsumer<T, U> void accept(T, U) (T, U) -> void
java.util.function Predicate<T> boolean test(T) T -> boolean
java.util.function BiPredicate<T, U> boolean test(T, U) (T, U) -> boolean
java.util.function Function<T, R> R apply(T) T -> R
java.util.function BiFunction<T, U, R> R apply(T, U) (T, U) -> R
java.util.function UnaryOperator<T> T apply(T) T -> T
java.util.function BinaryOperator<T> T apply(T, T) (T, T) -> T
java.lang Runnable void run() () -> void
java.util.concurrent Callable<V> V call() () -> V

1. Supplier

Supplier<T> は、引数なしで値を返す関数型インターフェースです。

T get()

基本例

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "Hello";

        System.out.println(supplier.get());
    }
}
Hello

重要ポイント

Supplier<T> は「毎回新しい値を返す」とは限りません。

例えば、次のように毎回同じ値を返しても Supplier です。

Supplier<String> supplier = () -> "Java";

一方で、毎回違う値を返すこともできます。

Supplier<Double> random = () -> Math.random();

System.out.println(random.get());
System.out.println(random.get());

Java Goldポイント

Supplier<T>引数なし・戻り値あり です。

Supplier<String> s1 = () -> "Java"; // OK

これはNGです。

// 引数を取っているためNG
// Supplier<String> s2 = name -> "Hello, " + name;

2. Consumer

Consumer<T> は、引数を1つ受け取り、戻り値を返さない関数型インターフェースです。

void accept(T t)

基本例

import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println(s);

        consumer.accept("Java");
    }
}
Java

重要ポイント

Consumer は副作用を前提とした処理で使われます。

副作用とは、例えば以下のような処理です。

  • コンソールに出力する
  • ログを書く
  • コレクションに追加する
  • オブジェクトの状態を変更する
Consumer<String> printer = s -> System.out.println("value = " + s);

forEachでよく使う

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        names.forEach(name -> System.out.println(name));
    }
}

forEach の引数は Consumer<T> と考えることができます。

Java Goldポイント

Consumer<T> は戻り値が void です。

ただし、次のようなコードはコンパイルできます。

Consumer<String> c = s -> s.length();

一見、s.length()int を返すためNGに見えます。

しかし、メソッド呼び出し式は「式文」として扱えるため、戻り値を捨てる形で Consumer に代入できます。

一方、次はNGです。

// returnで値を返しているためNG
// Consumer<String> c = s -> { return s.length(); };

これもNGです。

// 単なる値リテラルはvoid互換ではないためNG
// Consumer<String> c = s -> 100;

3. BiConsumer

BiConsumer<T, U> は、引数を2つ受け取り、戻り値を返さない関数型インターフェースです。

void accept(T t, U u)

基本例

import java.util.function.BiConsumer;

public class Main {
    public static void main(String[] args) {
        BiConsumer<String, Integer> printer =
                (name, age) -> System.out.println(name + " : " + age);

        printer.accept("Alice", 20);
    }
}
Alice : 20

Map#forEachでよく使う

import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = Map.of(
                "Alice", 90,
                "Bob", 80
        );

        scores.forEach((name, score) ->
                System.out.println(name + " = " + score));
    }
}

重要ポイント

Map#forEach はキーと値の2つを受け取るため、Consumer ではなく BiConsumer です。

map.forEach((key, value) -> { ... });

4. Predicate

Predicate<T> は、引数を1つ受け取り、booleanを返す関数型インターフェースです。

boolean test(T t)

基本例

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> isPositive = x -> x > 0;

        System.out.println(isPositive.test(10));
        System.out.println(isPositive.test(-1));
    }
}
true
false

Stream#filterでよく使う

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(-2, -1, 0, 1, 2);

        numbers.stream()
                .filter(x -> x > 0)
                .forEach(System.out::println);
    }
}

filter の引数は Predicate<T> です。

重要ポイント

Predicate<T> は必ずbooleanを返します。

Predicate<String> isEmpty = s -> s.isEmpty(); // OK
Predicate<String> isLong = s -> s.length() >= 5; // OK

これはNGです。

// intを返しているためNG
// Predicate<String> p = s -> s.length();

and / or / negate

Predicate には条件を組み合わせるメソッドがあります。

Predicate<Integer> isPositive = x -> x > 0;
Predicate<Integer> isEven = x -> x % 2 == 0;

Predicate<Integer> positiveAndEven = isPositive.and(isEven);

System.out.println(positiveAndEven.test(4)); // true
System.out.println(positiveAndEven.test(3)); // false

Java Goldポイント

andor は短絡評価します。

Predicate<Integer> p1 = x -> x > 0;
Predicate<Integer> p2 = x -> {
    System.out.println("p2 evaluated");
    return x % 2 == 0;
};

System.out.println(p1.and(p2).test(-1));

この場合、p1false なので、p2 は評価されません。


5. BiPredicate

BiPredicate<T, U> は、引数を2つ受け取り、booleanを返す関数型インターフェースです。

boolean test(T t, U u)

基本例

import java.util.function.BiPredicate;

public class Main {
    public static void main(String[] args) {
        BiPredicate<String, String> same =
                (a, b) -> a.equals(b);

        System.out.println(same.test("Java", "Java"));
        System.out.println(same.test("Java", "Gold"));
    }
}
true
false

重要ポイント

BiPredicate は「2つの値で条件判定する」ときに使います。

BiPredicate<String, Integer> hasLength =
        (str, length) -> str.length() == length;

System.out.println(hasLength.test("Java", 4)); // true

6. Function

Function<T, R> は、引数を1つ受け取り、結果を返す関数型インターフェースです。

R apply(T t)

基本例

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> length = s -> s.length();

        System.out.println(length.apply("Java"));
    }
}
4

Stream#mapでよく使う

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob");

        names.stream()
                .map(name -> name.length())
                .forEach(System.out::println);
    }
}

map の引数は Function<T, R> です。

重要ポイント

Function<T, R> は型変換・値の変換に使います。

Function<String, Integer> f = s -> s.length();

この場合、String を受け取り、Integer を返します。

String -> Integer

compose と andThen

Function には、関数を合成するメソッドがあります。

import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        Function<String, String> trim = s -> s.trim();
        Function<String, Integer> length = s -> s.length();

        System.out.println(trim.andThen(length).apply(" Java "));
    }
}
4

andThen

trim.andThen(length)

これは、先に trim を実行し、その結果に length を実行します。

trim -> length

compose

length.compose(trim)

これは、先に trim を実行し、その結果に length を実行します。

trim -> length

Java Goldポイント

andThencompose は実行順序が逆になりやすいので注意です。

f.andThen(g)  // fの後にg
f.compose(g) // gの後にf

7. BiFunction

BiFunction<T, U, R> は、引数を2つ受け取り、1つの結果を返す関数型インターフェースです。

R apply(T t, U u)

基本例

import java.util.function.BiFunction;

public class Main {
    public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> add =
                (a, b) -> a + b;

        System.out.println(add.apply(3, 5));
    }
}
8

重要ポイント

BiFunction<T, U, R> は型パラメータが3つあります。

BiFunction<T, U, R>
  • T:1つ目の引数
  • U:2つ目の引数
  • R:戻り値

例えば、次のように引数と戻り値の型がすべて異なっても構いません。

BiFunction<String, Integer, String> repeat =
        (str, count) -> str.repeat(count);

System.out.println(repeat.apply("Java", 3));
JavaJavaJava

8. UnaryOperator

UnaryOperator<T> は、1つの引数を受け取り、同じ型の結果を返す関数型インターフェースです。

Function<T, T> の特殊版です。

T apply(T t)

基本例

import java.util.function.UnaryOperator;

public class Main {
    public static void main(String[] args) {
        UnaryOperator<Integer> increment = x -> x + 1;

        System.out.println(increment.apply(10));
    }
}
11

重要ポイント

UnaryOperator<T> は入力型と戻り値型が同じです。

UnaryOperator<String> upper = s -> s.toUpperCase(); // OK
UnaryOperator<Integer> plusOne = x -> x + 1;        // OK

これはNGです。

// Stringを受け取ってintを返しているためNG
// UnaryOperator<String> length = s -> s.length();

この場合は Function<String, Integer> を使います。

Function<String, Integer> length = s -> s.length();

9. BinaryOperator

BinaryOperator<T> は、同じ型の引数を2つ受け取り、同じ型の結果を返す関数型インターフェースです。

BiFunction<T, T, T> の特殊版です。

T apply(T t1, T t2)

基本例

import java.util.function.BinaryOperator;

public class Main {
    public static void main(String[] args) {
        BinaryOperator<Integer> add = (a, b) -> a + b;

        System.out.println(add.apply(10, 20));
    }
}
30

Stream#reduceでよく使う

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        Integer sum = numbers.stream()
                .reduce(0, (a, b) -> a + b);

        System.out.println(sum);
    }
}
15

重要ポイント

BinaryOperator<T> は、引数2つも戻り値もすべて同じ型です。

BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
BinaryOperator<String> concat = (a, b) -> a + b;

一方、引数や戻り値の型が異なる場合は BiFunction<T, U, R> を使います。

BiFunction<String, Integer, String> repeat =
        (s, count) -> s.repeat(count);

10. Runnable

Runnable は、引数なし・戻り値なしで処理を実行する関数型インターフェースです。

void run()

Runnablejava.lang パッケージにあるため、importは不要です。

基本例

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("running");

        task.run();
    }
}
running

Threadで使う例

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("thread task");

        Thread thread = new Thread(task);
        thread.start();
    }
}

重要ポイント

Runnable は戻り値を返せません。

Runnable r = () -> System.out.println("Hello"); // OK

これはNGです。

// RunnableはvoidなのでNG
// Runnable r = () -> "Hello";

また、Runnable#run()throws Exception を宣言していません。

そのため、チェック例外をそのまま投げるラムダ式は書けません。

// InterruptedExceptionを処理していないためNG
// Runnable r = () -> Thread.sleep(1000);

書くなら、ラムダ式の中で例外処理をします。

Runnable r = () -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

11. Callable

Callable<V> は、引数なし・戻り値ありのタスクを表す関数型インターフェースです。

V call() throws Exception

基本例

import java.util.concurrent.Callable;

public class Main {
    public static void main(String[] args) throws Exception {
        Callable<String> task = () -> "result";

        System.out.println(task.call());
    }
}
result

ExecutorServiceで使う例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Callable<String> task = () -> "result";

        Future<String> future = executor.submit(task);

        System.out.println(future.get());

        executor.shutdown();
    }
}

重要ポイント

Callable は戻り値を返せて、チェック例外も投げられます。

Callable<String> c = () -> {
    Thread.sleep(1000);
    return "done";
};

Thread.sleep(1000)InterruptedException を投げますが、Callable#call()throws Exception を宣言しているため、このラムダ式は書けます。


Runnable / Callable / Supplier の違い

Java Goldでは、次の3つがかなり混同しやすいです。

インターフェース 引数 戻り値 チェック例外 用途
Runnable なし なし そのまま投げられない 処理だけ実行
Callable<V> なし あり 投げられる 結果を返すタスク
Supplier<T> なし あり そのまま投げられない 値の供給

覚え方

Runnable : () -> void
Callable : () -> V throws Exception
Supplier : () -> T

Java Goldポイント

CallableSupplier はどちらも「引数なし・戻り値あり」です。

ただし、Callablejava.util.concurrent パッケージにあり、非同期処理や Future と組み合わせて使われます。

Callable<String> callable = () -> "result";
Supplier<String> supplier = () -> "value";

Stream APIとの対応

Java Goldでは、Stream APIのメソッドがどの関数型インターフェースを受け取るかが重要です。

Stream API 対応する関数型インターフェース 役割
filter Predicate<T> 条件に合う要素を残す
map Function<T, R> 要素を変換する
forEach Consumer<T> 各要素に処理を行う
peek Consumer<T> 中間操作として要素を覗く
reduce BinaryOperator<T> 要素を1つにまとめる
generate Supplier<T> 値を生成する
collect Supplier / BiConsumer / BiConsumer 可変コンテナに集約する

filter:Predicate

list.stream()
        .filter(x -> x > 0);

x -> x > 0Predicate<T> です。

T -> boolean

map:Function

list.stream()
        .map(x -> x.toString());

x -> x.toString()Function<T, R> です。

T -> R

forEach:Consumer

list.stream()
        .forEach(x -> System.out.println(x));

x -> System.out.println(x)Consumer<T> です。

T -> void

reduce:BinaryOperator

list.stream()
        .reduce((a, b) -> a + b);

(a, b) -> a + bBinaryOperator<T> です。

(T, T) -> T

generate:Supplier

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream.generate(() -> Math.random())
                .limit(3)
                .forEach(System.out::println);
    }
}

() -> Math.random()Supplier<T> です。

() -> T

重要ポイント

Stream.generate() は無限Streamを生成するため、通常は limit() と組み合わせます。


collectで登場する3つの関数型インターフェース

collect では、以下の3つが登場します。

<R> R collect(
    Supplier<R> supplier,
    BiConsumer<R, ? super T> accumulator,
    BiConsumer<R, R> combiner
)

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> result = List.of("A", "B", "C").stream()
                .collect(
                        ArrayList::new,
                        ArrayList::add,
                        ArrayList::addAll
                );

        System.out.println(result);
    }
}
[A, B, C]

役割

引数 関数型インターフェース 役割
ArrayList::new Supplier<R> 結果を入れる箱を作る
ArrayList::add BiConsumer<R, T> 箱に要素を追加する
ArrayList::addAll BiConsumer<R, R> 箱同士を結合する

Java Goldポイント

collectSupplierBiConsumer が同時に出てくるため、かなり狙われやすいです。


プリミティブ特化型も押さえる

java.util.function には、intlongdouble などに対応したプリミティブ特化型もあります。

これはオートボクシングを避けるために使われます。

代表例

インターフェース 意味
IntPredicate int -> boolean intを判定する
IntConsumer int -> void intを処理する
IntSupplier () -> int intを供給する
IntFunction<R> int -> R intからRへ変換する
ToIntFunction<T> T -> int Tからintへ変換する
IntUnaryOperator int -> int intをintに変換する
IntBinaryOperator (int, int) -> int int同士を処理してintを返す

混同注意

IntFunction<R>ToIntFunction<T> は逆です。

IntFunction<String> f1 = i -> "value = " + i;
int -> String
ToIntFunction<String> f2 = s -> s.length();
String -> int

Java Goldで狙われるコンパイル可否

1. Supplierに引数ありラムダはNG

Supplier<String> s1 = () -> "OK"; // OK

// Supplierは引数を取らないためNG
// Supplier<String> s2 = x -> "NG";

2. Predicateはbooleanを返す必要がある

Predicate<String> p1 = s -> s.isEmpty(); // OK

// intを返しているためNG
// Predicate<String> p2 = s -> s.length();

3. FunctionとUnaryOperatorの違い

Function<String, Integer> f = s -> s.length(); // OK

// UnaryOperator<String>はStringを返す必要があるためNG
// UnaryOperator<String> u = s -> s.length();

4. BinaryOperatorはすべて同じ型

BinaryOperator<Integer> b1 = (a, b) -> a + b; // OK

// 引数と戻り値の型がすべてStringでなければならないためNG
// BinaryOperator<String> b2 = (a, b) -> a.length() + b.length();

この場合は BiFunction<String, String, Integer> です。

BiFunction<String, String, Integer> b =
        (a, c) -> a.length() + c.length();

5. Callableはチェック例外を投げられる

Callable<String> c = () -> {
    Thread.sleep(1000);
    return "OK";
};

これはOKです。

一方、Supplier ではそのまま書けません。

// Supplierのgetはthrows Exceptionを持たないためNG
// Supplier<String> s = () -> {
//     Thread.sleep(1000);
//     return "OK";
// };

メソッド参照との対応

関数型インターフェースは、ラムダ式だけでなくメソッド参照でも使えます。

Consumer

Consumer<String> printer = System.out::println;

printer.accept("Java");
String -> void

Function

Function<String, Integer> length = String::length;

System.out.println(length.apply("Java"));
String -> Integer

Supplier

Supplier<ArrayList<String>> supplier = ArrayList::new;

ArrayList<String> list = supplier.get();
() -> ArrayList<String>

BinaryOperator

BinaryOperator<Integer> max = Integer::max;

System.out.println(max.apply(10, 20));
(Integer, Integer) -> Integer

最後に:暗記用まとめ

基本4種類

名前 覚え方
Supplier 供給する
Consumer 消費する
Predicate 判定する
Function 変換する

Biが付くと引数が2つ

名前
Consumer<T> T -> void
BiConsumer<T, U> (T, U) -> void
Predicate<T> T -> boolean
BiPredicate<T, U> (T, U) -> boolean
Function<T, R> T -> R
BiFunction<T, U, R> (T, U) -> R

Operatorは同じ型

名前
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T

Runnable / Callable / Supplier

名前 例外
Runnable () -> void チェック例外をそのまま投げられない
Callable<V> () -> V チェック例外を投げられる
Supplier<T> () -> T チェック例外をそのまま投げられない

まとめ

Java Goldでは、関数型インターフェースを名前だけで覚えるのではなく、ラムダ式の形で覚えることが重要です。

特に、以下は頻出です。

filter   -> Predicate<T>
map      -> Function<T, R>
forEach  -> Consumer<T>
reduce   -> BinaryOperator<T>
generate -> Supplier<T>
collect  -> Supplier + BiConsumer + BiConsumer

また、次の違いは混同しやすいため、重点的に押さえるとよいです。

Function<T, R>      : T -> R
UnaryOperator<T>    : T -> T

BiFunction<T,U,R>   : (T, U) -> R
BinaryOperator<T>   : (T, T) -> T

Runnable            : () -> void
Callable<V>         : () -> V throws Exception
Supplier<T>         : () -> T

関数型インターフェースは、ラムダ式・メソッド参照・Stream APIの基礎になるため、抽象メソッド名とラムダ式の形をセットで覚えるのがおすすめです。


参考

  • Oracle Java SE API Specification
  • java.util.function パッケージ
  • FunctionalInterface
  • Supplier
  • Consumer
  • Predicate
  • Function
  • BiFunction
  • UnaryOperator
  • BinaryOperator
  • Runnable
  • Callable
  • Stream
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?