白猫のメモ帳

JavaとかJavaScriptとかHTMLとか機械学習とか。

お前もFunctionalInterfaceにしてやろうか

こんばんは。

タイトルの元ネタ、誰のセリフか知らなかったのですが、デーモン閣下だったのですね。
あと、藁人形だと思ってたけど蝋人形でした。ごめんなさい。

しばらくC#ネタを書いていましたが、たまにはJavaネタを。
安心感。

で、Java8で追加された機能にインタフェースのデフォルト実装があるのですが、
これのちょっとした小技を見ていきます。

関数型インタフェース


Java8でラムダ式が導入されたことに伴い、
単一のメソッドのみを持つインタフェースを
関数型インタフェース(FunctionalInterface)として扱うことができるようになりました。

通常、関数型インタフェースには「FunctionalInterface」アノテーションを付けますが、
ついていないからと言ってラムダを使えないというわけではありません。
Javaの注釈ってそういうもんです。Overrideとかね。)

例えば、引数を取らずに結果だけを返す関数として「Supplier」クラスがありますが、
こんな定義になっています。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

で、こんな風にラムダ式が使えるわけです。

// ただ「あいうえお」という文字列を返すだけの関数
Supplier<String> supplier = () -> "あいうえお";


もしくはメソッド参照を使って、こんな感じです。

// ただ「かきくけこ」という文字列を返すだけの関数
private String k() {
    return "かきくけこ";
}

private void hoge() {
    Supplier<String> supplier = this::k;
}


ところで同じ関数型の「Consumer」クラスを見てみると、こんなことになっています。

@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」は「accept」を呼び出しているので、
ひとつの関数定義を渡すことによって複数のメソッドが利用できるようになるわけですね。

public static void main(String[] args) {

    Consumer<PrintStream> consumer1 = ps -> ps.println("ABC");
    Consumer<PrintStream> consumer2 = ps -> ps.println("DEF");

    consumer1.andThen(consumer2).accept(System.out);
}

 

誰だって関数型インタフェースになれるよ


さて、関数型インタフェースにはdefaultメソッドを作ってもいいことがわかりました。

ということはですよ、複数の抽象メソッドを持つ関数型インタフェースで、
1メソッド以外をサブインタフェースでデフォルト実装したら、
これはもう関数型インタフェースになるんじゃないのっていう気がしてきます。

試してみましょう。

public interface Greeter {
    String greeting();
    String customizedGreeting();
}

@FunctionalInterface
public interface PiyoGreeter extends Greeter {
    @Override
    default String customizedGreeting() {
        return greeting() + "ぴよ";
    }
}

こんな風にdefaultでオーバーライドして、

private static String greet(PiyoGreeter greeter) {
    return greeter.customizedGreeting();
}

public static void main(String[] args) {
    System.out.println(greet(() -> "おはよう"));    // おはようぴよ
    System.out.println(greet(() -> "こんばんは"));  // こんばんはぴよ
}

おぉ、できた!

できた・・・けど、どんな時に使うんでしょうね。
うーん。