Ch4. ラムダ式とメソッド参照

ラムダ式と匿名クラス

第2章で学んだ匿名クラス(≒ローカルクラス)とラムダ式は基本的に同じ仕組みになっている。 ラムダ式で利用できる外部の変数のスコープは匿名クラスのスコープと変わらない。

package Chapter4;

import java.util.function.UnaryOperator;

public class LambdaScope {
    public static void main(String[] args) {
        final int outside0 = 0;
        int outside1       = 100;
        int outside2       = 200;
        int outside3;

        outside1 = 200;

        UnaryOperator<String> lambda1 = (String variable) -> {
            System.out.println(outside0);
            System.out.println(outside1); //'Local variable outside1 defined in an enclosing scope must be final or effectively final'
            //outside2 = outside2 + 100;  //'Local variable outside2 defined in an enclosing scope must be final or effectively final'
            //outside3 = 100;             //'Local variable outside3 defined in an enclosing scope must be final or effectively final'
            return variable + "!!";
        };

        outside1 = outside1 + 130;
    }

}

メソッド参照

lambda式内で呼び出されるメソッドが1つだけの場合、引数を省略して更に簡潔に記述できる方法としてメソッド参照という方法が用意されている。

クラスメソッド参照

クラスメソッドに対するメソッド参照を実施する場合、【クラス名】::【クラスメソッド名】でメソッド参照を行える。

       Comparator<Integer> lambda3 = (num1, num2) -> Integer.compare(num1,num2);
        lambda3.compare(100, 200);

        Comparator<Integer> lambda4 = Integer::compare;
        lambda4.compare(100,200);

ラムダ式内で呼び出すメソッドの数が一つであれば、引数を複数とるメソッドであってもメソッド参照では引数を省略して書ける。

インスタンスメソッド参照

インスタンスメソッドに対するメソッド参照は【インスタンス名】::【インスタンスメソッド名】となる。

       List<Integer> list = Arrays.asList(1,2,3);
        list.forEach( x -> System.out.println(x * x));//'1,4,9'
        list.forEach(System.out::println);            //'1,2,3'

上記の例では、Systemクラスのoutインスタンスprintlnインスタンスメソッドに対して数字を渡していく処理である。forEachの引数にはCunsumerを渡すので、戻り値は無い。

上記の例と異なり、どのインスタンスに対するインスタンスメソッド呼び出しになるかわからない場合だけ【クラス名】::【インスタンスメソッド名】で記述することになっている。

       UnaryOperator<String> lambda = str -> str.toLowerCase();
        UnaryOperator<String> lambda2 = String::toLowerCase;

このように「どのインスタンスに対するインスタンスメソッド呼び出しになるかわからない」場合は引数の渡し方に注意する必要がある。 第一引数が「呼び出しを行うインスタンス」の型であり、続く第二引数が「引数」になる。

       BiFunction<String, Integer, Character> lambda5 = String::charAt;
        lambda5.apply("Hello Airbus", 3);
        //BiFunction<Integer, String, Character> lambda5 = String::charAt;//'The type String does not define charAt(Integer, String) that is applicable here'

コンストラクタ参照

【クラス名】::【new】でコンストラクタ参照出来る。

package Chapter4;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

class MyClass{
    static int number = 0;
    MyClass(){
        System.out.println("コンストラクタ呼出" + number++);
    }

    MyClass(int number){
        System.out.println("コンストラクタ呼出 :" + number++);
    }

    void myMethod(){
        System.out.println("インスタンスメソッド呼出");
    }
}

public class ConstractorReferenceSample {
    public static void main(String[] args) {
        Supplier<MyClass> object = MyClass::new;
        object.get(); //'コンストラクタ呼出0'
        object.get().myMethod();//'コンストラクタ呼出1' 'インスタンスメソッド呼出'

        Function<Integer,MyClass> object2 = MyClass::new;
        object2.apply(10);//コンストラクタ呼出 :10

        Supplier<List<MyClass>> object3 = ArrayList<MyClass>::new;
        System.out.println(object3.get());//'[]'
    };

}

プリミティブ型を引数と返却値に設定する関数型インターフェース

プリミティブ型をパラメータとして受け取ったり、プリミティブ型の返却値を戻す関数型インターフェースも用意されている。 プリミティブ型を返却する場合はToInt(Double)が、プリミティブ型を受け取る場合はInt(Dobuble)がインターフェース名を接頭語としてついている。

jdk/IntFunction.java at master · openjdk/jdk · GitHub

これらの関数型インターフェースを利用した場合、ラムダ式のパラメーターとしてラッパークラスを指定するとコンパイルエラーになるので注意する。

package Chapter4;

import java.util.function.BooleanSupplier;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;
import java.util.function.ToIntBiFunction;
import java.util.function.ToIntFunction;

public class PrimitiveInterface {
    public static void main(String[] args) {
        IntFunction<MyClass> object = MyClass::new;//'intを受け取り、MyClassを返す'
        object.apply(100);

        //IntFunction<MyClass> object7 = (Integer param) -> {return new MyClass(param);};
        //'Lambda expression's parameter param is expected to be of type int'
        //object7.apply(100);

        ToIntFunction<MyClass> object5 = param -> param.method2(); //'MyClass型を受け取り、int型を返却する'
        ToIntBiFunction<MyClass, Integer> object6 = (param, param2) -> {return param.method2(param2);};


        IntUnaryOperator object2 = number -> number + 100;
        System.out.println(object2.applyAsInt(100));

        IntPredicate object3 = number -> number < 100 ? true : false;
        System.out.println(object3.test(1000));

        BooleanSupplier object4 = ()-> true;

    }

}

class MyClass{
    MyClass(int param){
        System.out.println(param);
    }

    int method (int param){
        System.out.println(param);
        return param;
    }
    int method2 () {
        return 100;
    }
    int method2(int number) {
        return 100 + number;
    }
}