Javaの「関数型インターフェース (Functional Interface)」とは、「1つだけ抽象メソッドを持つインターフェース」のことです。Java 8以降に導入されたラムダ式やメソッド参照などの機能と深い関係があり、これらを活用するための“土台”となる存在です。
まずは「インターフェース」とは何かをおさらい
Javaにおける「インターフェース」は、「クラスが実装すべきメソッドの型だけを定義した設計書」のようなものです。たとえば、インターフェースに
interface MyInterface { void doSomething(); }
というメソッドが宣言されていたら、これをimplements
するクラスは必ずdoSomething()
というメソッドを定義(実装)しなければなりません。
「抽象メソッド」の意味
「抽象メソッド」というのは、中身(処理内容)を書かずに、メソッドの宣言(名前や引数、戻り値)だけを書いたメソッドのことです。たとえば
void doSomething();
のように、本体となるブロック { ... }
がなく、「こういう名前のメソッドで、この型の引数を取って、この型の戻り値を返すよ」という宣言だけをするものです。
関数型インターフェースの定義
関数型インターフェースは「抽象メソッドがちょうど1つだけ」あるインターフェースです。
(静的メソッドやデフォルトメソッドなどが複数あってもかまわない。重要なのは“抽象メソッドが1つ”という点。)
Javaが提供する代表例
Runnable
インターフェース- 抽象メソッドが
run()
1つだけ
- 抽象メソッドが
@FunctionalInterface public interface Runnable { public abstract void run(); }
Callable
インターフェース- 抽象メソッドが
call()
1つだけ
- 抽象メソッドが
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
これらはJava 8以前から存在していましたが、「一つだけ抽象メソッドを持つ」構造だったため、Java 8以降は「関数型インターフェース」に分類されるようになりました。
なぜ関数型インターフェースが重要か?
ラムダ式との関係
Java 8で追加された「ラムダ式」は、より簡潔に処理を記述できる機能です。たとえば以下のような書き方が可能です。
// 通常のクラス実装 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello from Runnable"); } }; // ラムダ式 Runnable r2 = () -> System.out.println("Hello from Lambda");
ここでのRunnable
は抽象メソッドがrun()
だけの関数型インターフェースです。ラムダ式の形で () -> { ... }
と書くと、その中の処理が run()
メソッドの中身になるイメージです。
つまり、ラムダ式を使うためには、受け取る型が関数型インターフェースである必要があるのです。
自分で関数型インターフェースを作る
例1:処理を抽象化したインターフェース
@FunctionalInterface interface MyFunction { void execute(); }
@FunctionalInterface
アノテーションは、このインターフェースが関数型インターフェースであることをコンパイラに保証させるためのものです(必須ではないですが、付けるとエラー検出に役立ちます)。- 抽象メソッドは
execute()
1つだけなので、これが関数型インターフェースになります。
このインターフェースを使ってラムダ式を書くことができます:
MyFunction f = () -> System.out.println("Hello from MyFunction"); f.execute(); // "Hello from MyFunction" と表示
例2:引数や戻り値がある場合
1つの抽象メソッドがあっても、以下のように複雑な引数や戻り値を設定してOKです。
@FunctionalInterface interface Calculator { int calc(int x, int y); }
これをラムダ式で実装すると、
Calculator addition = (x, y) -> x + y; Calculator multiplication = (x, y) -> x * y; System.out.println(addition.calc(3, 4)); // 7 System.out.println(multiplication.calc(3, 4)); // 12
となります。 Calculator
インターフェースは「整数を2つ渡されたとき、どんな計算をして整数を返すか」という動作を抽象化し、それをラムダ式で自由に書き換えられるのがメリットです。
Java標準ライブラリにある代表的な関数型インターフェース
Java標準ライブラリ(java.util.function
パッケージなど)には、以下のように用意された汎用的な関数型インターフェースがたくさんあります。これらは非常によく使われるので覚えておくと便利です。
Function<T, R>
T
型の入力を受け取ってR
型の出力を返す。- 抽象メソッド:
R apply(T t)
Consumer<T>
T
型の入力を受け取って、何も返さず消費(利用)する。- 抽象メソッド:
void accept(T t)
Supplier<T>
T
型の出力(値)を供給し、入力をとらない。- 抽象メソッド:
T get()
Predicate<T>
T
型の入力を受け取って真偽値(boolean
)を返す。- 抽象メソッド:
boolean test(T t)
BiFunction<T, U, R>
T
とU
の2つの入力を受け取って、R
を返す。- 抽象メソッド:
R apply(T t, U u)
これらは、何かを入力して処理し、結果を返すといった汎用的なパターンを網羅しています。
自分でインターフェースを定義せずとも、これらを活用することで生産性を高められます。
まとめ
- 関数型インターフェースとは?
- 抽象メソッドが1つだけのインターフェース。
- ラムダ式やメソッド参照を使うための“受け皿”となる存在。
- なぜ重要か?
- Java 8以降のラムダ式をサポートする仕組み。
- イベント駆動やコールバック処理など、「ちょっとした処理を切り替えたい」場面で非常に便利。
- 使い方のポイント
@FunctionalInterface
を付けることでコードの安全性と可読性を高められる。- Java標準ライブラリにもよく使われる関数型インターフェースが用意されている。 (
Function
,Consumer
など)
- 初心者へのアドバイス
- インターフェースや抽象メソッドの考え方をしっかり理解しよう。
- 自分で関数型インターフェースを定義し、ラムダ式で実装するサンプルをいくつか試してみると自然に理解が深まる。