Java8 Project Lambda (ラムダ式)

Java8のProject Lambda(ラムダ式)について再確認したメモです。

Java8で追加されたラムダ式についてテストを書いて確認しました。

ラムダ式

ラムダ式Java関数型言語の様に、関数を第1級オブジェクトの様に扱えるようにするための仕様です。
第1級オブジェクトというのは関数の引数に関数を渡したり、戻り値として関数を戻したりできる
オブジェクトのことです。
匿名クラス構文を簡潔に記述するための手法のようなものですが、
厳密には違うらしいです。その辺はまだ勉強中…(*´-`)

関数型インターフェース

関数型インターフェースは実装されるメソッドが1つだけ定義されたインターフェースです。
それにより、インタフェースのどのメソッドをラムダ式で定義しようとしているかが明確になります。
ラムダ式で扱えるのは関数型インターフェースのみとなります。

ラムダ式、関数型インターフェースについてはOracleの下記ドキュメントがかなり詳しく書いてあります。
Java 8:ラムダ式、パート1
Java 8:ラムダ式、パート2

匿名クラスとラムダ式

匿名クラスをラムダ式で書いてみます。

BinaryOperatorインターフェースを実装した匿名クラスを書いてみます。
BinaryOperatorインターフェースは2つの引数をとり、1つの結果を返します。
BiFunctionインターフェースのサブインターフェースですが、BiFunctionと違う点は、
2つの同じ型の引数を取り、同じ型の結果を返すということです。

apply(T, U)という抽象メソッドを持っており、これを実装します。
単純にIntegerの値を2つとり、足し算してIntegerの結果を返す処理にしてみます。

匿名クラスで書いた場合。

    // 匿名クラス
    BinaryOperator<Integer> anonymous = new BinaryOperator<Integer>() {
        @Override
        public Integer apply(Integer i1, Integer i2) {
            return i1 + i2;
        }
    };

下記の様に実行します。

    int actual = anonymous.apply(5, 6);

これをラムダ式で記述してみます。
匿名クラスのnew演算子やクラスの定義を省略したような記述になります。
匿名クラスの実装を簡潔に記述するための手法と考えるといいと思います。

    // ラムダ式
    BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
        return i1 + i2;
    };

下記の様に実行します。

    int actual = lambda.apply(5, 6);

テストコード。

    public static class 匿名クラスとラムダ式 {
        // 匿名クラス
        BinaryOperator<Integer> anonymous = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer i1, Integer i2) {
                return i1 + i2;
            }
        };

        // ラムダ式
        BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
            return i1 + i2;
        };

        @Test
        public void 匿名クラス実行() throws Exception {
            int actual = anonymous.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ラムダ式実行() throws Exception {
            int actual = lambda.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }
    }

ラムダ式の省略記法

ラムダ式では型推論を用いて型やカッコなどを省略して記述することが出来ます。

先ほど記述したBinaryOperatorは引数の型と戻り値の型が同じなため、
ラムダ式の引数の型は冗長な気がします。
そのため、下記の様に引数の型を省略して記述できます。

    // ラムダ式
    BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
        return i1 + i2;
    };

    // 引数の型の省略
    BinaryOperator<Integer> argsTypeLess = (i1, i2) -> {
        return i1 + i2;
    };

さらに、メソッドの処理が足し算してreturnするだけなのに、ブレースで囲っているのも
冗長な気がします。
そのため、1つの式で記述できる場合は、下記の様にブレースとreturnが省略可能です。

    // ブレースの省略
    BinaryOperator<Integer> braceLess = (i1, i2) -> i1 + i2;

下記のようなラムダ式のように、引数が1つだけの場合、同様に引数の型を省略できますが、
引数のカッコも冗長なため、省略できます。
上記のBinaryOperatorのように引数を2つ以上とる場合は、引数のカッコは省略できません。

    // 文字列を2回繰り返すメソッド
    Function<String, String> doubleString = (String str) -> {
        return str + str;
    };

    // 引数の()を省略
    Function<String, String> parenLess = str -> {
        return str + str;
    };

さらに同様にブレースも省略できます。

    // ブレースの省略
    Function<String, String> braceLess2 = str -> str + str;

テストコード

    public static class ラムダ式の省略記法 {
        // ラムダ式
        BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
            return i1 + i2;
        };

        // 引数の型の省略
        BinaryOperator<Integer> argsTypeLess = (i1, i2) -> {
            return i1 + i2;
        };

        // ブレースの省略
        BinaryOperator<Integer> braceLess = (i1, i2) -> i1 + i2;

        @Test
        public void 引数の型が省略可能であること() throws Exception {
            int actual = argsTypeLess.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ブレースが省略可能であること() throws Exception {
            int actual = braceLess.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        // 文字列を2回繰り返すメソッド
        Function<String, String> doubleString = (String str) -> {
            return str + str;
        };

        // 引数の()を省略
        Function<String, String> parenLess = str -> {
            return str + str;
        };

        // ブレースの省略
        Function<String, String> braceLess2 = str -> str + str;

        @Test
        public void 文字列が2回繰り返されること() throws Exception {
            String actual = doubleString.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void 引数のカッコが省略可能であること() throws Exception {
            String actual = parenLess.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ブレースが省略可能であること2() throws Exception {
            String actual = braceLess2.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }
    }

実質的final

ラムダ式ではローカル変数にアクセスできるのは、finalな変数のみです。

finalなので参照のみ可能で、再代入はできません。

    final int numFinal = 10;
    IntUnaryOperator opeFinal = x -> x + numFinal;

毎回ローカル変数にfinalを付与するのは大変なので、
ラムダ式では、再代入されないローカル変数の場合、
実質的にfinal変数とみなして、finalなしでもfinal変数として見なされるようになります。

    int num = 20;
    IntUnaryOperator ope = x -> x + num;

テストコード。

    public static class 実質的finalの確認 {
        @Test
        public void 明示的なfinalのローカル変数が参照できること() throws Exception {
            final int numFinal = 10;
            IntUnaryOperator opeFinal = x -> x + numFinal;

            int actual = opeFinal.applyAsInt(5);
            int expected = 15;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void 実質的finalのローカル変数が参照できること() throws Exception {
            int num = 20;
            IntUnaryOperator ope = x -> x + num;

            int actual = ope.applyAsInt(10);
            int expected = 30;
            assertThat(actual).isEqualTo(expected);
        }
    }

テストコード

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

LambdaTest.java

package javase8;

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

import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;

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

@RunWith(Enclosed.class)
public class LambdaExampleTest {
    public static class 匿名クラスとラムダ式 {
        // 匿名クラス
        BinaryOperator<Integer> anonymous = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer i1, Integer i2) {
                return i1 + i2;
            }
        };

        // ラムダ式
        BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
            return i1 + i2;
        };

        @Test
        public void 匿名クラス実行() throws Exception {
            int actual = anonymous.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ラムダ式実行() throws Exception {
            int actual = lambda.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }
    }

    public static class ラムダ式の省略記法 {
        // ラムダ式
        BinaryOperator<Integer> lambda = (Integer i1, Integer i2) -> {
            return i1 + i2;
        };

        // 引数の型の省略
        BinaryOperator<Integer> argsTypeLess = (i1, i2) -> {
            return i1 + i2;
        };

        // ブレースの省略
        BinaryOperator<Integer> braceLess = (i1, i2) -> i1 + i2;

        @Test
        public void 引数の型が省略可能であること() throws Exception {
            int actual = argsTypeLess.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ブレースが省略可能であること() throws Exception {
            int actual = braceLess.apply(5, 6);
            int expected = 11;
            assertThat(actual).isEqualTo(expected);
        }

        // 文字列を2回繰り返すメソッド
        Function<String, String> doubleString = (String str) -> {
            return str + str;
        };

        // 引数の()を省略
        Function<String, String> parenLess = str -> {
            return str + str;
        };

        // ブレースの省略
        Function<String, String> braceLess2 = str -> str + str;

        @Test
        public void 文字列が2回繰り返されること() throws Exception {
            String actual = doubleString.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void 引数のカッコが省略可能であること() throws Exception {
            String actual = parenLess.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void ブレースが省略可能であること2() throws Exception {
            String actual = braceLess2.apply("abc");
            String expected = "abcabc";
            assertThat(actual).isEqualTo(expected);
        }
    }

    public static class 実質的finalの確認 {
        @Test
        public void 明示的なfinalのローカル変数が参照できること() throws Exception {
            final int numFinal = 10;
            IntUnaryOperator opeFinal = x -> x + numFinal;

            int actual = opeFinal.applyAsInt(5);
            int expected = 15;
            assertThat(actual).isEqualTo(expected);
        }

        @Test
        public void 実質的finalのローカル変数が参照できること() throws Exception {
            int num = 20;
            IntUnaryOperator ope = x -> x + num;

            int actual = ope.applyAsInt(10);
            int expected = 30;
            assertThat(actual).isEqualTo(expected);
        }
    }
}

終わり。

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