@FunctionalInterface を一言でいうと
@FunctionalInterface は、
「このインターフェースは“関数型インターフェース”として使うつもりですよ」とコンパイラに宣言するためのアノテーション
です。
関数型インターフェースとは、「抽象メソッドをちょうど 1 つだけ持つインターフェース」のことです。
この条件を満たしたインターフェースは、ラムダ式やメソッド参照でインスタンス化できます。
@FunctionalInterface を付けると、
「抽象メソッドが 2 個以上になったらコンパイルエラーにする」という“安全装置”が働きます。
そもそも関数型インターフェースとは何か
抽象メソッドが 1 つだけのインターフェース
関数型インターフェースの定義はシンプルです。
「抽象メソッドが 1 つだけのインターフェース」。
例えばこうです。
@FunctionalInterface
public interface MyFunc {
int apply(int x);
}
Java抽象メソッド apply が 1 つなので、関数型インターフェースです。
このインターフェースは、ラムダ式で直接実装できます。
MyFunc f = x -> x * 2;
int result = f.apply(3); // 6
Javaここで x -> x * 2 というラムダ式は、「int を 1 つ受け取って int を返す関数」として MyFunc に代入されています。
ラムダ式と 1 対 1 で対応する「関数の型」
Java は「関数そのもの」を直接型として持っていません。
そこで、「抽象メソッド 1 つだけのインターフェース」を“関数の型”として扱う、という設計を取りました。
RunnableCallable<V>Comparator<T>
など、Java 8 以前からあるインターフェースも、「抽象メソッドが 1 つ」であるため、関数型インターフェースとしてラムダ式に使えます。
@FunctionalInterface の役割とメリット
役割 1:そのインターフェースが「関数型」であることの宣言
@FunctionalInterface をインターフェースに付けると、
「このインターフェースは関数型インターフェースとして使うつもりです」
と宣言できます。
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
Javaこの宣言のおかげで、読む人にも「これはラムダ用の“関数の入れ物”なんだな」とすぐ伝わります。
役割 2:抽象メソッドが 1 つだけであることをコンパイラが保証
一番大事なのはここです。
@FunctionalInterface が付いたインターフェースに、
うっかり抽象メソッドを 2 つ以上書いてしまうと、コンパイルエラーになります。
@FunctionalInterface
public interface BadFunc {
void m1();
// void m2(); // これをコメントアウト解除するとコンパイルエラー
}
Javaこれによって、
「関数型インターフェースとして設計したつもりが、
誰かが後から抽象メソッドを追加して“関数型ではなくなってしまう”」
という事故を防げます。
なお、@FunctionalInterface を付けていなくても、
抽象メソッドが 1 つしかなければ、そのインターフェースは関数型インターフェースとして扱われます。
ただし、その場合はコンパイラがチェックしてくれないので、明示的に付けておく方が安心です。
具体例で @FunctionalInterface の意味を体感する
例1:自作の関数型インターフェースとラムダ式
シンプルな例からいきます。
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
}
Javaこのインターフェースは、「int を受け取って boolean を返す関数の型」です。
使う側はこうです。
public class IntPredicateExample {
public static void main(String[] args) {
IntPredicate isEven = x -> x % 2 == 0;
IntPredicate isPositive = x -> x > 0;
System.out.println(isEven.test(4)); // true
System.out.println(isPositive.test(-1)); // false
}
}
Javaここで isEven も isPositive も、
「int を受け取って boolean を返す処理」
としてラムダ式で定義され、IntPredicate 型に代入されています。
@FunctionalInterface を付けておくことで、
「このインターフェースは“1 メソッドだけをラムダで実装させたい型”なんだな」
という意図が明確になります。
例2:誤って抽象メソッドを増やしたとき
上の IntPredicate に、間違って別の抽象メソッドを追加してみます。
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
// boolean other(int v); // 追加するとコンパイルエラー
}
Javaother を追加すると、コンパイラが「@FunctionalInterface なのに抽象メソッド 2 つあるよ」と怒ってくれます。
もしアノテーションを付けていなかったら、
「抽象メソッド 2 つのインターフェース」を作れてしまい、
それを関数型インターフェースだと勘違いしてラムダ式で使おうとしてハマる…ということが起きかねません。
@FunctionalInterface とデフォルトメソッド/static メソッド
抽象メソッド以外はいくつあってもよい
関数型インターフェースの条件は、
「抽象メソッドが 1 つだけ」
です。
逆に言うと、
デフォルトメソッド
static メソッドObject から継承したメソッドのオーバーライド
これらはいくつあっても構いません。
例えば、次は正しい関数型インターフェースです。
@FunctionalInterface
public interface MyFunc {
int apply(int x);
default void log(int x) {
System.out.println("x = " + x);
}
static void hello() {
System.out.println("Hello");
}
}
Javaapply だけが抽象メソッドで、log はデフォルト実装付き、hello は static なので、
「抽象メソッドは 1 つ」という条件を満たしています。
Object のメソッド(toString, equals, hashCode など)をオーバーライドしている場合も、それらは「抽象メソッドのカウントから除外される」と仕様で決まっています。
標準 API における @FunctionalInterface の利用
Java 標準の関数型インターフェースたち
java.util.function パッケージには、多数の関数型インターフェースが用意されています。
例えばこういうものです。
Supplier<T>Consumer<T>Function<T, R>Predicate<T>
これらの多くには @FunctionalInterface が付いています。
また、Runnable や Comparator<T> など、古くからあるインターフェースにも
Java 8 以降で @FunctionalInterface が付けられました。
このアノテーションのおかげで、
「このインターフェースはラムダ式で使うためのものだ」
「抽象メソッドは 1 つだけであると保証されている」
という情報が、コードレベルで明示されています。
@FunctionalInterface をいつ付けるべきか
自分で「ラムダ用のインターフェース」を定義するときは必ず付ける
判断基準はシンプルです。
「このインターフェースを、“関数として渡すための型”として使うか?」
もし答えが「はい」なら、@FunctionalInterface を付けるのがおすすめです。
CallbackValidator<T>Handler<T>Converter<F, T>
のような、「何か 1 つの処理を表す」インターフェースを自作するときは、
それが関数型であることを明示した方が、後から読む人にも、自分自身にも親切です。
逆に、通常の“オブジェクトのインターフェース”として設計しているなら、
無理に関数型にする必要はありません。
まとめ:@FunctionalInterface を自分の言葉で整理する
@FunctionalInterface を初心者向けにまとめると、
「このインターフェースは“抽象メソッド 1 つだけ”の関数型インターフェースですよ、とコンパイラと人間に約束する印」
です。
それによって、
ラムダ式やメソッド参照でインスタンス化できる「関数の型」になる
抽象メソッドを 2 つ以上宣言するとコンパイルエラーになり、誤りにすぐ気付ける
デフォルトメソッドや static メソッドはいくつあってもよい
というメリットが得られます。
