@FunctionalInterface

アノテーションを調べていたら@FunctionalInterfaceというアノテーションを知りましたので、これが何なのかちょっと調べてみました。

tl;dr

結論から言うと@FunctionalInterfaceは、抽象メソッド(Abstract method)を1つだけもつ関数型インターフェイスに付与されるアノテーション のことだそうです。

これだけだとなんのこっちゃって感じですが、そういうものだそうです。
ちなみに@FunctionalInterfaceを付与したインターフェイスに抽象メソッドが2つ以上あるとコンパイル時に怒られます。

実際に使ってみる

たとえば以下のようなインターフェイスがあるとします。

interface StringHandler {
    void handle(String str);
}

このインターフェイスは、handle(String str)という抽象メソッド(実装がない)が1つだけ定義されていますので、@FunctionalInterfaceを付けることができます。

@FunctionalInterface
interface StringHandler {
    void handle(String str);
}

でも、なんで@FunctionalInterfaceを付ける必要があるのでしょうか。

それは、このインターフェイスが、Java 8から追加されたラムダ式に使うことが出来る関数型インターフェイス(そのものずばりFunctional Interfaceですね)であることを明示するため です。

実際、ラムダ式で良く使われるインターフェイスConsumerの実装を見ると@FunctionalInterfaceが付いてました。

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

このインターフェイスにはandThen(Consumer<? super T> after)という関数もありますが、これはdefaultが付いているため、抽象メソッドではなく、デフォルトの実装がある関数のため抽象メソッドとは別になっていて、そのためこのインターフェイスは@FunctionalInterfaceだということになります。

ちなみにConsumerは、何かの値を受け取ってなにかの処理をするというシンプルなもので、以下のような使い方をします。

// 与えられた値を二乗したものを出力する
Consumer<Integer> squareProcessor = integer -> {
    System.out.println(integer * integer);
};
squareProcessor.accept(100); // 10000

関数型インターフェイスの使い方

最初に紹介したStringHandlerというインターフェイスは、String型を受け取ってなにかの処理をする関数を扱うインターフェイスになっていて、下記のように使うことができます。

// 与えられた値を単純に出力する
StringHandler printHandler = str -> {
    System.out.println(str);
};
printHandler.handle("foo"); // foo

printHandlerstrを受け取って、標準出力にその文字列を出力する処理になっています。

他にも下記のような感じでも使えますね。

// 与えられた値を「:」で区切って、その最初の要素を出力する
StringHandler splitCommaHandler = str -> {
    String[] stuff = str.split(":");
    System.out.println(stuff[0]);
};
splitCommaHandler.handle("bar:baz:blah"); // bar

splitCommaHandlerは、文字列を受け取って「:」で文字列を区切って、その最初の要素を標準出力に出す感じです。

このように何かを受け取って(なにも受け取らない場合もある)、何かの処理をするラムダ式やメソッド参照の代入先になれるインターフェイスのことを関数型インターフェイスといいます。

標準の関数型インターフェイスたち

Consumerファミリー

Javaには先程ご紹介したConsumer以外にも、このような関数型インターフェイスが標準でいくつも用意されています。

例えばIntConsumerという関数型インターフェイスは、int型を受け取って何かをするインターフェイスです。
下記のような感じで使えます。

// 与えられた値を2倍して出力する
IntConsumer doubleProcessor = num -> {
    System.out.println(num * 2);
};
doubleProcessor.accept(10); // 20

他にも、LongConsumerDoubleConsumerなどもあります。

あとはBiConsumerといって、引数を2つ(Bi)取って処理をする関数型インターフェイスもあります。
使い方は下記のような感じです。

// 与えられた2つの値を掛け算して出力する
BiConsumer<Integer, Integer> multiplyProcessor = (num1, num2) -> {
    System.out.println(num1 * num2);
};
multiplyProcessor.accept(3, 4); // 12

Supplierファミリー

Consumer系 以外にも、単純になにかを取得するためのSupplierというインターフェイスもあります。

下記は単純に”foo”という文字列を取得する関数の例です。

// fooという文字列を取得できる
Supplier<String> fooSupplier = () -> {
    return "foo";
};
String fooValue = fooSupplier.get(); // foo
System.out.println(fooValue);

もちろんSupplierには、IntSupplierLongSupplierDoubleSupplierなどもあります。

Functionファミリー

また、Functionというのもあります。
これは、引数と返り値を定義することができる関数型インターフェイスです。

以下はInteger型を受け取って、それに”Hello, “を付加したString型の文字列を返す関数の例です。

// 与えられた値に"Hello, "を付加して返す
Function<Integer, String> addHello = num -> {
    return "Hello, " + num;
};
String addHelloResult = addHello.apply(4949);
System.out.println(addHelloResult);

Predicateファミリー

Predicateは何かを評価してその成否を扱うためのインターフェイスです。

以下は、与えられた文字列に”a”がはいっているかどうかを確認する処理の例です。

Predicate<String> containsA = str -> {
    return str.contains("a");
};
if (containsA.test("hoge fuga")) {
    System.out.println("contains");
} else {
    System.out.println("not contains");
}

andThen()って?

ちなみにご紹介したConsumerという関数型インターフェイスにはandThen()という関数がデフォルト実装されていました。

これは、続けて処理をすることができる便利な機能のようです。

下記のように、最後のaccept()で渡した値を、doubleProcessortrebleProcessorquadrupleProcessorの順に処理をするという使い方をすることができます。

// 与えられた値を2倍して出力する
IntConsumer doubleProcessor = num -> {
    System.out.println(num * 2);
};

// 与えられた値を3倍して出力する
IntConsumer trebleProcessor = num -> {
    System.out.println(num * 3);
};

// 与えられた値を4倍して出力する
IntConsumer quadrupleProcessor = num -> {
    System.out.println(num * 4);
};

doubleProcessor
    .andThen(trebleProcessor)
    .andThen(quadrupleProcessor)
    .accept(5); // 10 15 20

関数型インターフェイスにはこのような便利な使い方ができるデフォルト実装があったりするので、興味があったら調べてみて下さい。

おわり

ということで今回は@FunctionalInterfaceというアノテーションについて調べてみました。

今まであまりラムダ式とかを使ってなかったのでこれを機にもうちょっと使ってみようかと思いました。