メソッド参照

メソッド参照について

Stremの振り返り

以前のStreamAPIの記事で、こんなコードを書きました。

IntStream.rangeClosed(1,30).boxed().map(value-> {
    String result = "";
    result = value % 3 == 0 && value % 5 == 0 ? "FizzBuzz" :
             value % 3 == 0 ? "Fizz" :
             value % 5 == 0 ? "Buzz" :
             value.toString();
    return result;
}).forEach(System.out::println);

一番最後のこの部分(System.out::prnitln)
これは、メソッド参照と呼ばれる構文です。

ラムダ式の場合

関数型インターフェースに代入する際、ラムダ式だとこんな感じ。

public class Studay {
    public static void main(String[] args) {
        Function<Integer,String> funcHello = (num) -> "Hello" + num.toString();
        System.out.println(funcHello.apply(1));//Hello1
    }
}

メソッド参照

ラムダ式でも十分簡潔ですが、もっと簡潔に書けるのがメソッド参照。 関数型インターフェースと引数、戻り値、型が一致していれば、メソッドを代入することができます。

public class Studay {
    public static void main(String[] args) {
        Function<Integer,String> funcHello = Studay::Hello;
        System.out.println(funcHello.apply(1));
    }
    public static String Hello(Integer num){
        return "Hello" + num.toString(); //Hello1
    };
}

staticメソッドの場合は、クラス名::メソッド名。
非staticメソッドの場合は、インスタンス名::メソッド名で記述します。
最初の例に戻ると、forEachの部分でメソッド参照を使っています。
ドキュメントを読むと…

forEach
void forEach(Consumer<? super T> action)
このストリームの各要素に対してアクションを実行します。

Consumerってのを使ってますね。Consumerを調べてみると…。

インタフェースConsumer
型パラメータ:T - オペレーションの入力の型 @FunctionalInterface
public interface Consumer
単一の入力引数を受け取って結果を返さないオペレーションを表します。Consumerは他のほとんどの関数型インタフェースと異なり、副作用を介して動作することを期待されます。
これは、accept(Object)を関数メソッドに持つ関数型インタフェースです。

引数は1つ受け取るけど、戻り値はないようです。
ラムダ式だと、こんな使い方。

public class Studay {
    public static void main(String[] args) {
        Consumer<String> Hello = (str) -> System.out.println(str);
        Hello.accept("Hello");//Hello
    }
}

System.out.println()は、引数を受け取って結果を返さずに標準出力を行うメソッドなので、Consumerと一致しています。
ってことで、これをメソッド参照で書くと、こんな感じ。

public class Studay {
    public static void main(String[] args) {
        Consumer<String> Hello = System.out::println;
        Hello.accept("Hello");
    }
}

ってことは、forEachはConsumerを期待してるので、System.out.printlnが使えそうです。

IntStream.rangeClosed(1,30).boxed().map(value-> {
    String result = "";
    result = value % 3 == 0 && value % 5 == 0 ? "FizzBuzz" :
             value % 3 == 0 ? "Fizz" :
             value % 5 == 0 ? "Buzz" :
             value.toString();
    return result;
}).forEach(System.out::println);

もちろん、途中のmapはFunctionを期待してるので、mapの中身もメソッド化してしまえばメソッド参照ができます。

public class Studay {
    public static void main(String[] args) {
        IntStream.rangeClosed(1,30).boxed().map(Studay::FizzBuzz).forEach(System.out::println);
    }
    public static String FizzBuzz(Integer i){
        String result = "";
        result = i % 3 == 0 && i % 5 == 0 ? "FizzBuzz" :
                 i % 3 == 0 ? "Fizz" :
                 i % 5 == 0 ? "Buzz" :
                 i.toString();
        return result;
    }
}

スッキリしましたね。 メソッド参照を使うと、よりコードを美しく書くことができそうです。
ただ、私のようなミジンコだと、引数の数やどんな処理をしているのかがぱっと見判断し辛いかも。慣れなきゃ…。メソッドの命名も重要だなぁ。