Java - Lambda式と関数型インタフェース

Lambda expressionはJava 8に導入され、関数型インタフェース(/ja/java8-functional-interface/)オブジェクトを表します。つまり、ラムダ式は抽象メソッド(インタフェース)を実装しますが、名前がないので匿名関数(Anonymous Function)に似ています。

Lambda式は、匿名関数(クラス、オブジェクト)を使用するよりも少ないコードで同じ内容を実装できます。関数型プログラミングでは、ラムダ式でコードを簡潔にし、読みやすさを向上させることができます。

匿名関数の詳細については、Java - 匿名クラス(Anonymous class)を参照してください。

1.ラムダ式と匿名関数の違い

まず、ラムダ式と匿名関数を使用しない場合は、次のようにクラスのオブジェクトを直接作成して関数に引数として渡す必要があります。 Math インタフェースを実装した MyMath クラスのオブジェクトが一度だけ使用され再利用されない場合、この方法は非効率的である可能性があります。この場合、匿名関数を使用すると、クラスを毎回実装する必要がなくなり、少ないコードで同じ内容を実装できます。

public class Example {

    interface Math {
        int sum(int n1, int n2);
    }

    static class MyMath implements Math {
        public int sum(int n1, int n2) {
            return n1 + n2;
        }
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {
        Math math = new MyMath();
        int result = doSomething(math);
        System.out.println(result);  // 30
    }
}

以下の例は、匿名関数を使用して同じ内容を実装した例です。一度だけ使用されるオブジェクトを作成するためにクラスを実装する必要はありません。そのため、匿名オブジェクトにはクラス名がありません。

public class Example1 {

    interface Math {
        abstract int sum(int n1, int n2);
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething(new Math() {  // Anonymous function
            @Override
            public int sum(int n1, int n2) {
                return n1 + n2;
            }
        });
        System.out.println(result);  // 30
    }
}

以下の例は、ラムダ式を使用して同じ内容を実装した例です。匿名関数と非常に似ていますが、不要な宣言部はすべて省略されており、インタフェースの関数の実装部だけを定義しています。 コードが最も簡潔です。

public class Example2 {

    interface Math {
        int sum(int n1, int n2);
    }

    static int doSomething(Math math) {
        return math.sum(10, 20);
    }

    public static void main(String[] args) {

        int result = doSomething((n1, n2) -> n1 + n2);  // Lambda
        System.out.println(result);  // 30
    }
}

2. ラムダ式のSynta

通常、ラムダ式は () -> expression のように、一行で書く。ラムダ式が何らかの値を返す必要がある場合は、式の実行結果が返されます。

(parameter) -> expression

引数が多い場合は、次のように引数を追加できます。

(parameter1, parameter2) -> expression

式を1行で実装するのが難しい場合は、 { }を使用して複数行で実装できます。代わりに明示的にreturnを入力する必要があります。

(parameter1, parameter2) -> { code block }

上記のラムダ式の例を括弧を使用して複数行で実装すると、次のように実装できます。

int result = doSomething((n1, n2) -> {
    int res1 = n1 * 10;
    int res2 = n2 * 10;
    return res1 + res2;
});

3. ラムダ式の特

上記で一般クラス、匿名クラス、ラムダ式で実装した例を見てみました。 ラムダ式の特徴をまとめてみると次のようになります。

  • クラスを実装する必要はありません
  • インタフェース(抽象メソッド)を実装する
  • Abstract クラスの場合、匿名関数は可能ですが、ラムダ式は実装できません
  • 引数を渡すことができ、結果を返すことができます
  • コードが簡潔

4. ラムダ式と関数型インタフェース (Functional Interface)

Lambda式は、上記の例のように直接定義したインタフェースに使用できますが、すでに定義されている関数型インタフェースにも使用できます。

関数型インタフェースは以下のような関数を提供しています。したがって、直接定義せずにこれらの関数を使用するだけです。

  • Runnable:引数を受け取らず、戻り値がありません
  • Supplier :引数を受けずにT型オブジェクト
  • 消費者:T型オブジェクトを引数として受け取り、戻り値がありません
  • 機能<T, R>:T型オブジェクトを引数として受け取り、R型オブジェクトリ
  • Predicate :Tタイプ客因子として受け取り、ブールリー

関数型インタフェースの詳細については、Java8 - 関数型インタフェース(Functional Interface)についてを参照してください。

Supplier<T>を例に挙げると、以下のコードで引数を受けずにStringを返すSupplierのラムダ式を実装しました。 get() 呼び出し時に、ラムダ式で定義された実装が動作しながら String が返されます。

Supplier<String> getString = () -> "Happy new year!";
System.out.println(getString.get());  // Happy new year!

別の例として、上記で実装した sum() の例を関数型インタフェースとラムダ式を使って再実装しましょう。

BiFunction<T, U, R>はT型とU型の引数を渡し、R型のオブジェクトを返す関数型インタフェースです。 既存に直接定義した Math インタフェースは BiFunction に置き換えることができるため、削除してもかまいません。 apply() を呼び出すと、渡された引数とともにラムダ式が実行され、結果が返されます。

import java.util.function.BiFunction;

public class Example2 {

    static int doSomething(BiFunction<Integer, Integer, Integer> sum) {
        return sum.apply(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething((n1, n2) -> n1 + n2);  // Lambda
        System.out.println(result);  // 30
    }
}

匿名関数を使用して実装する場合は、次のように実装できます。 しかし、関数型インタフェースを大量に使用する場合、匿名関数を使用すると、コードが長くなり、読みやすさが低下する可能性があります。

import java.util.function.BiFunction;

public class Example2 {

    static int doSomething(BiFunction<Integer, Integer, Integer> sum) {
        return sum.apply(10, 20);
    }

    public static void main(String[] args) {
        int result = doSomething(new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer n1, Integer n2) {
                return n1 + n2;
            }
        });  // Anonymous function
        System.out.println(result);  // 30
    }
}
codechachaCopyright ©2019 codechacha