24
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?

関数型インターフェース入門 — Function / Predicate / Consumer / Supplier を整理する

24
Posted at

はじめに

Java 8でラムダ式とStream APIが導入されたとき、合わせて関数型インターフェースという概念が登場しました。

List<String> names = List.of("田中", "佐藤", "鈴木");
names.stream()
     .filter(name -> name.length() == 2)
     .forEach(System.out::println);

filter()forEach() に渡しているラムダ式は、実は関数型インターフェースを実装したものです。

この記事では、java.util.function パッケージに定義されている代表的な4つの関数型インターフェース(Function / Predicate / Consumer / Supplier)を整理します。ラムダ式やStream APIをより深く理解したい方、Java Goldを目指している方にも役立つ内容です。


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

抽象メソッドが1つだけのインターフェースのことです。@FunctionalInterface アノテーションをつけることで、コンパイラがその制約を強制してくれます。

@FunctionalInterface
interface MyFunction {
    int execute(int a, int b); // 抽象メソッドは1つだけ
}

ラムダ式は関数型インターフェースを簡潔に実装する構文です。

// 匿名クラス
MyFunction add = new MyFunction() {
    @Override
    public int execute(int a, int b) { return a + b; }
};

// ラムダ式(同じ意味)
MyFunction add = (a, b) -> a + b;

毎回自分で定義しなくても済むよう、Javaが標準で用意しているのが java.util.function パッケージです。


4つの代表的な関数型インターフェース

インターフェース 引数 戻り値 位置付け
Function<T, R> T(あり) R(あり) 変換・加工
Predicate<T> T(あり) boolean 条件判定
Consumer<T> T(あり) void(なし) 消費・副作用
Supplier<T> なし T(あり) 生成・供給

それぞれ詳しく見ていきましょう。


Function<T, R> — 値を受け取って別の値に変換する

シグネチャ

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
  • T:引数の型
  • R:戻り値の型
  • メソッド:apply()

使い方

// String を受け取って Integer を返す
Function<String, Integer> strToLength = str -> str.length();

System.out.println(strToLength.apply("Hello")); // 5
System.out.println(strToLength.apply("田中"));  // 2
// Integer を受け取って String を返す
Function<Integer, String> intToStr = num -> "番号: " + num;

System.out.println(intToStr.apply(42)); // 番号: 42

Stream APIでの使用例

List<String> names = List.of("田中", "佐藤", "鈴木");

// map() は Function<T, R> を受け取る
List<Integer> lengths = names.stream()
    .map(name -> name.length()) // Function<String, Integer>
    .collect(Collectors.toList());

System.out.println(lengths); // [2, 2, 2]

andThen() で関数をつなぐ

Function<String, Integer> toLength = str -> str.length();
Function<Integer, String> toMessage = len -> "文字数は " + len + " です";

// andThen で連結
Function<String, String> combined = toLength.andThen(toMessage);
System.out.println(combined.apply("Hello")); // 文字数は 5 です

Predicate<T> — 条件を判定してbooleanを返す

シグネチャ

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
  • T:引数の型
  • 戻り値:boolean
  • メソッド:test()

使い方

// 文字数が2文字かどうかを判定
Predicate<String> isTwoChars = str -> str.length() == 2;

System.out.println(isTwoChars.test("田中"));  // true
System.out.println(isTwoChars.test("佐々木")); // false
// 数値が正かどうかを判定
Predicate<Integer> isPositive = num -> num > 0;

System.out.println(isPositive.test(5));  // true
System.out.println(isPositive.test(-3)); // false

Stream APIでの使用例

List<String> names = List.of("田中", "佐々木", "鈴木", "伊藤");

// filter() は Predicate<T> を受け取る
List<String> twoCharNames = names.stream()
    .filter(name -> name.length() == 2) // Predicate<String>
    .collect(Collectors.toList());

System.out.println(twoCharNames); // [田中, 鈴木, 伊藤]

and() / or() / negate() で条件を組み合わせる

Predicate<String> isTwoChars  = str -> str.length() == 2;
Predicate<String> startsWith田 = str -> str.startsWith("田");

// AND条件
Predicate<String> combined = isTwoChars.and(startsWith田);
System.out.println(combined.test("田中")); // true
System.out.println(combined.test("田辺三菱")); // false(2文字ではない)

// 否定
Predicate<String> notTwoChars = isTwoChars.negate();
System.out.println(notTwoChars.test("佐々木")); // true

Consumer<T> — 値を受け取って何かするが値を返さない

シグネチャ

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
  • T:引数の型
  • 戻り値:void(なし)
  • メソッド:accept()

「消費する(consume)」という名前の通り、値を受け取って何らかの処理(ログ出力・DB保存など)をしますが、値を返しません。

使い方

Consumer<String> printName = name -> System.out.println("こんにちは、" + name + "さん");

printName.accept("田中"); // こんにちは、田中さん
printName.accept("佐藤"); // こんにちは、佐藤さん
// リストに追加するConsumer
List<String> result = new ArrayList<>();
Consumer<String> addToList = item -> result.add(item);

addToList.accept("Apple");
addToList.accept("Banana");
System.out.println(result); // [Apple, Banana]

Stream APIでの使用例

List<String> names = List.of("田中", "佐藤", "鈴木");

// forEach() は Consumer<T> を受け取る
names.forEach(name -> System.out.println(name)); // Consumer<String>

// メソッド参照で書くとさらに短く
names.forEach(System.out::println);

Supplier<T> — 引数なしで値を生成して返す

シグネチャ

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • 引数:なし
  • 戻り値:T
  • メソッド:get()

「供給する(supply)」という名前の通り、引数を受け取らずに値を生成して返します。

使い方

// 現在時刻を返すSupplier
Supplier<LocalDateTime> nowSupplier = () -> LocalDateTime.now();

System.out.println(nowSupplier.get()); // 現在時刻が出力される
System.out.println(nowSupplier.get()); // 呼ぶたびに現在時刻を生成
// 新しいListを生成するSupplier
Supplier<List<String>> listSupplier = () -> new ArrayList<>();

List<String> list1 = listSupplier.get(); // 新しいArrayList
List<String> list2 = listSupplier.get(); // また新しいArrayList

OptionalのorElseGet()での使用例

Optional<String> optional = Optional.empty();

// orElseGet() は Supplier<T> を受け取る
String value = optional.orElseGet(() -> "デフォルト値"); // Supplier<String>
System.out.println(value); // デフォルト値

orElse()orElseGet() の違いは、orElseGet() は値が必要なときだけ Supplier を呼び出すため、生成コストが高いオブジェクトに向いています。


まとめ

インターフェース メソッド 引数 戻り値 主な使用場面
Function<T, R> apply() T R map() での変換
Predicate<T> test() T boolean filter() での絞り込み
Consumer<T> accept() T void forEach() での処理
Supplier<T> get() なし T orElseGet() での生成

Stream APIのメソッドがどの関数型インターフェースを受け取っているかを意識すると、ラムダ式の書き方が自然と身につきます。Java Goldでも頻出のテーマなので、apply() / test() / accept() / get() のメソッド名とセットで覚えておきましょう。

24
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
24
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?