Java8 メソッド参照 (method reference)

javaのメソッド参照を試したメモです。

目次

メソッド参照

java8でメソッド参照が出来るようになりました。
メソッド参照を使用するとラムダ式を書かずに直接メソッドを実行できます。

クラスメソッド参照

クラスのstaticメソッドを実行する場合は、クラスメソッド参照で実行します。

下記のstaticメソッドを用意します。

    public static String printWithBrace(String str) {
        return "{" + str + "}";
    }

クラスメソッド参照の場合、クラス名にセミコロン2つをつけてメソッドを呼びます。
ラムダ式と異なり、引数は記述しません。
apply()で実行時に引数を与えます。

    // ラムダ式
    UnaryOperator<String> lambda = str -> ClassMethodReference.printWithBrace(str);
    String lambdaStr = lambda.apply("abc");
    // メソッド参照
    UnaryOperator<String> methodRef = ClassMethodReference::printWithBrace;
    String methodRefStr = methodRef.apply("abc");

テストコード。

    @Test
    public void クラスメソッド参照() throws Exception {
        // ラムダ式
        UnaryOperator<String> lambda = str -> ClassMethodReference.printWithBrace(str);
        String lambdaStr = lambda.apply("abc");
        // メソッド参照
        UnaryOperator<String> methodRef = ClassMethodReference::printWithBrace;
        String methodRefStr = methodRef.apply("abc");

        assertThat(lambdaStr).isEqualTo("{abc}");
        assertThat(methodRefStr).isEqualTo("{abc}");
    }

引数を2つとる下記のようなstaticメソッドの場合。

    public static String printWithBi(String prefix, String suffix) {
        return prefix + ":" + suffix;
    }

これも同様に、ラムダ式と異なり、引数は記述しません。
apply()で実行時に引数を2つ与えます。

    // ラムダ式
    BinaryOperator<String> lambda = (pre, suf) -> ClassMethodReference.printWithBi(pre, suf);
    String lambdaStr = lambda.apply("aaa", "bbb");
    // メソッド参照
    BinaryOperator<String> methodRef = ClassMethodReference::printWithBi;
    String methodRefStr = methodRef.apply("aaa", "bbb");

テストコード。

    @Test
    public void 複数引数のクラスメソッド参照() throws Exception {
        // ラムダ式
        BinaryOperator<String> lambda = (pre, suf) -> ClassMethodReference.printWithBi(pre, suf);
        String lambdaStr = lambda.apply("aaa", "bbb");
        // メソッド参照
        BinaryOperator<String> methodRef = ClassMethodReference::printWithBi;
        String methodRefStr = methodRef.apply("aaa", "bbb");

        assertThat(lambdaStr).isEqualTo("aaa:bbb");
        assertThat(methodRefStr).isEqualTo("aaa:bbb");
    }
インスタンスメソッド参照

インスタンスメソッドも同様に記述できます。

Mapのputメソッドをラムダ式インスタンスメソッド参照で記述してみます。
Mapのputメソッドは引数を2つとり、戻り値を1つ返すので、BiFunction<T, U, R>インターフェースを使用します。
インスタンスメソッド参照も引数なしで記述します。
apply()で実行時に引数を指定します。

    // ラムダ式
    Map<Integer, String> map = new HashMap<>();
    BiFunction<Integer, String, String> lambda = (i, s) -> map.put(i, s);
    lambda.apply(1, "aaa");
    // インスタンスメソッド参照
    BiFunction<Integer, String, String> methodRef = map::put;
    methodRef.apply(2, "bbb");

テストコード。

    @Test
    public void インスタンスメソッド参照() throws Exception {
        // ラムダ式
        Map<Integer, String> map = new HashMap<>();
        BiFunction<Integer, String, String> lambda = (i, s) -> map.put(i, s);
        lambda.apply(1, "aaa");
        // インスタンスメソッド参照
        BiFunction<Integer, String, String> methodRef = map::put;
        methodRef.apply(2, "bbb");

        String getLambda = map.get(1);
        assertThat(getLambda).isEqualTo("aaa");

        String getMethodRef = map.get(2);
        assertThat(getMethodRef).isEqualTo("bbb");
    }

ラムダ式の引数のインスタンスメソッドを実行する場合も、
同様にインスタンスメソッド参照で記述できます。
上記の場合は、Mapのインスタンスメソッドを参照していましたが、
下記の場合は、引数のStringのインスタンスメソッドを参照してます。

    // ラムダ式
    Function<String, String> func = str -> str.toUpperCase();
    String UpperStr = func.apply("abc");
    // インスタンスメソッド参照
    Function<String, String> func2 = String::toUpperCase;
    String UpperStr2 = func2.apply("abc");

テストコード。

    @Test
    public void クラス名を指定したインスタンスメソッド参照() throws Exception {
        // ラムダ式
        Function<String, String> func = str -> str.toUpperCase();
        String UpperStr = func.apply("abc");
        // インスタンスメソッド参照
        Function<String, String> func2 = String::toUpperCase;
        String UpperStr2 = func2.apply("abc");

        String expected = "ABC";
        assertThat(UpperStr).isEqualTo(expected);
        assertThat(UpperStr2).isEqualTo(expected);
    }
コンストラクタ参照

コンストラクタ参照を使用すると、newによるオブジェクト生成も同様に記述できます。

引数なしのコンストラクタ参照
下記のFooクラスを定義します。

    class Foo {
        Foo() {}
    }

コンストラクタ参照でオブジェクトを生成する場合、クラス名にセミコロン2つにnewと記述します。
引数なしでオブジェクトを返すため、Supplierインタフェースとなります。

    // ラムダ式
    Supplier<Foo> lambda = () -> new Foo();
    Foo fooLambda = lambda.get();
    // コンストラクタ参照
    Supplier<Foo> ref = Foo::new;
    Foo fooRef = ref.get();

テストコード。

    @Test
    public void コンストラクタ参照() throws Exception {
        // ラムダ式
        Supplier<Foo> lambda = () -> new Foo();
        Foo fooLambda = lambda.get();
        // コンストラクタ参照
        Supplier<Foo> ref = Foo::new;
        Foo fooRef = ref.get();

        assertThat(fooLambda).isInstanceOf(Foo.class);
        assertThat(fooRef).isInstanceOf(Foo.class);
    }

引数ありのコンストラクタ参照
下記のBarクラスを定義します。

    class Bar {
        String name;
        Bar(String name) {
            this.name = name;
        }
    }

こちらもコンストラクタ参照では引数は記述しません。
引数なしのコンストラクタ参照と異なり、引数をとるクラスなのでこちらはFunctionインタフェースとなります。
apply()で実行時に引数を与えます。

    // ラムダ式
    Function<String, Bar> lambda = str -> new Bar(str);
    Bar barLambda = lambda.apply("Lambda Bar");
    // コンストラクタ参照
    Function<String, Bar> ref = Bar::new;
    Bar barRef = ref.apply("Ref Bar");

テストコード。

    @Test
    public void 引数のあるコンストラクタ参照() {
        // ラムダ式
        Function<String, Bar> lambda = str -> new Bar(str);
        Bar barLambda = lambda.apply("Lambda Bar");
        // コンストラクタ参照
        Function<String, Bar> ref = Bar::new;
        Bar barRef = ref.apply("Ref Bar");

        assertThat(barLambda.name).isEqualTo("Lambda Bar");
        assertThat(barRef.name).isEqualTo("Ref Bar");
    }

ジェネリクスのあるコンストラクタ参照

下記のBazクラスを定義します。
このクラスはTの型引数を取ります。

    class Baz<T> {
        T name;
        T getName() {
            return name;
        }
        void setName(T name) {
            this.name = name;
        }
    }

引数を取らないクラスのためSupplierインターフェースになります。
Supplierの実型引数としてBaz<String>を指定します。

    // ラムダ式
    Supplier<Baz<String>> lambda = () -> new Baz<>();
    Baz<String> bazLambda = lambda.get();
    // コンストラクタ参照
    Supplier<Baz<String>> ref = Baz::new;
    Baz<String> bazRef = ref.get();

テストコード。

    @Test
    public void ジェネリクスを使用したコンストラクタ参照() throws NoSuchMethodException, NoSuchFieldException {
        // ラムダ式
        Supplier<Baz<String>> lambda = () -> new Baz<>();
        Baz<String> bazLambda = lambda.get();
        // コンストラクタ参照
        Supplier<Baz<String>> ref = Baz::new;
        Baz<String> bazRef = ref.get();

        assertThat(bazLambda).isInstanceOf(Baz.class);
        assertThat(bazRef).isInstanceOf(Baz.class);
    }

配列のコンストラクタ参照

配列の生成の場合、引数として配列のサイズ指定のためのintを取るため、
IntFunctionインターフェースになります。
IntFunctionの型引数として<String[]>のように配列の形で指定します。

    // ラムダ式
    IntFunction<String[]> func = (size) -> new String[size];
    String[] arr = func.apply(5);
    // コンストラクタ参照
    IntFunction<String[]> func2 = String[]::new;
    String[] arr2 = func2.apply(5);

テストコード。

    @Test
    public void 配列生成() {
        // ラムダ式
        IntFunction<String[]> func = (size) -> new String[size];
        String[] arr = func.apply(5);
        // コンストラクタ参照
        IntFunction<String[]> func2 = String[]::new;
        String[] arr2 = func2.apply(5);

        int length = 5;
        assertThat(arr.length).isEqualTo(length);
        assertThat(arr2.length).isEqualTo(length);
    }

テストコード

今回のテストコードの全体です。

MethodReferenceTest.java

package javase8;

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;
import java.util.function.*;
import java.util.stream.IntStream;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Enclosed.class)
public class MethodReferenceTest {
    public static class ClassMethodReference {

        public static String printWithBrace(String str) {
            return "{" + str + "}";
        }

        @Test
        public void クラスメソッド参照() throws Exception {
            // ラムダ式
            UnaryOperator<String> lambda = str -> ClassMethodReference.printWithBrace(str);
            String lambdaStr = lambda.apply("abc");
            // メソッド参照
            UnaryOperator<String> methodRef = ClassMethodReference::printWithBrace;
            String methodRefStr = methodRef.apply("abc");

            assertThat(lambdaStr).isEqualTo("{abc}");
            assertThat(methodRefStr).isEqualTo("{abc}");
        }

        public static String printWithBi(String prefix, String suffix) {
            return prefix + ":" + suffix;
        }

        @Test
        public void 複数引数のクラスメソッド参照() throws Exception {
            // ラムダ式
            BinaryOperator<String> lambda = (pre, suf) -> ClassMethodReference.printWithBi(pre, suf);
            String lambdaStr = lambda.apply("aaa", "bbb");
            // メソッド参照
            BinaryOperator<String> methodRef = ClassMethodReference::printWithBi;
            String methodRefStr = methodRef.apply("aaa", "bbb");

            assertThat(lambdaStr).isEqualTo("aaa:bbb");
            assertThat(methodRefStr).isEqualTo("aaa:bbb");
        }
    }

    public static class InstanceMethodReference {
        @Test
        public void インスタンスメソッド参照() throws Exception {
            // ラムダ式
            Map<Integer, String> map = new HashMap<>();
            BiFunction<Integer, String, String> lambda = (i, s) -> map.put(i, s);
            lambda.apply(1, "aaa");
            // インスタンスメソッド参照
            BiFunction<Integer, String, String> methodRef = map::put;
            methodRef.apply(2, "bbb");

            String getLambda = map.get(1);
            assertThat(getLambda).isEqualTo("aaa");

            String getMethodRef = map.get(2);
            assertThat(getMethodRef).isEqualTo("bbb");
        }

        @Test
        public void クラス名を指定したインスタンスメソッド参照() throws Exception {
            // ラムダ式
            Function<String, String> func = str -> str.toUpperCase();
            String UpperStr = func.apply("abc");
            // インスタンスメソッド参照
            Function<String, String> func2 = String::toUpperCase;
            String UpperStr2 = func2.apply("abc");

            String expected = "ABC";
            assertThat(UpperStr).isEqualTo(expected);
            assertThat(UpperStr2).isEqualTo(expected);
        }
    }

    public static class ConstructorReference {
        class Foo {
            Foo() {}
        }

        @Test
        public void コンストラクタ参照() throws Exception {
            // ラムダ式
            Supplier<Foo> lambda = () -> new Foo();
            Foo fooLambda = lambda.get();
            // コンストラクタ参照
            Supplier<Foo> ref = Foo::new;
            Foo fooRef = ref.get();

            assertThat(fooLambda).isInstanceOf(Foo.class);
            assertThat(fooRef).isInstanceOf(Foo.class);
        }

        class Bar {
            String name;
            Bar(String name) {
                this.name = name;
            }
        }

        @Test
        public void 引数のあるコンストラクタ参照() {
            // ラムダ式
            Function<String, Bar> lambda = str -> new Bar(str);
            Bar barLambda = lambda.apply("Lambda Bar");
            // コンストラクタ参照
            Function<String, Bar> ref = Bar::new;
            Bar barRef = ref.apply("Ref Bar");

            assertThat(barLambda.name).isEqualTo("Lambda Bar");
            assertThat(barRef.name).isEqualTo("Ref Bar");
        }
        
        class Baz<T> {
            T name;
            T getName() {
                return name;
            }
            void setName(T name) {
                this.name = name;
            }
        }

        @Test
        public void ジェネリクスを使用したコンストラクタ参照() throws NoSuchMethodException, NoSuchFieldException {
            // ラムダ式
            Supplier<Baz<String>> lambda = () -> new Baz<>();
            Baz<String> bazLambda = lambda.get();
            // コンストラクタ参照
            Supplier<Baz<String>> ref = Baz::new;
            Baz<String> bazRef = ref.get();

            assertThat(bazLambda).isInstanceOf(Baz.class);
            assertThat(bazRef).isInstanceOf(Baz.class);
        }

        @Test
        public void 配列生成() {
            // ラムダ式
            IntFunction<String[]> func = (size) -> new String[size];
            String[] arr = func.apply(5);
            // コンストラクタ参照
            IntFunction<String[]> func2 = String[]::new;
            String[] arr2 = func2.apply(5);

            int length = 5;
            assertThat(arr.length).isEqualTo(length);
            assertThat(arr2.length).isEqualTo(length);
        }
    }
}

こんなとこです。

ソースは一応あげときました。
github.com

終わり。