ラムダ式は変数に代入して再利用することができますが、そのフィルタ条件などは固定されています。
Java8 ラムダ式を変数に代入して再利用する
そのため、そのフィルタ条件を変えたい場合、別々の式を定義する必要があるかもしれません。
例えば、
”文字列の長さが5の文字列のみ”をフィルタするラムダ式と、
”文字列の長さが8の文字列のみ”をフィルタするラムダ式が欲しい場合、以下のように2つの式を定義する方法が考えられます。
// 文字列の長さが5
final Predicate<String> lengthEqualFive = name -> name.length() == 5;
// 文字列の長さが8
final Predicate<String> lengthEqualEight = name -> name.length() == 8;
条件が常に固定であれば問題ありませんが、そのアプリケーションが柔軟に条件の変更をしたい場合、その数だけ式を定義することになるかもしれません。
この条件(この例では5か8)をパラメータとして受け取ることが出来れば、このような重複を避けることが出来ます。
例えば、以下のようなメソッドを定義する方法が考えられます。
Predicate<String> lengthEqualWith(final Integer expectsLength) {
return name -> name.length == expectsLength;
}
単にラムダ式を変数に代入する代わりに、ラムダ式を返すメソッドに引数を渡し、その引数をラムダ式に適用するような形です。
lengthEqualWith() を呼び出す時に、expectsLength をパラメータとして渡してあげることで、柔軟に条件を変更することが可能となります。
以下のリストを使ってフィルタしてみます。
final List<String> months =
Arrays.asList("January", "February", "March", "April", "May", "June", "July", "Augast", "September", "October", "November", "December");
以下のように lengthEqualWith(N) を適用しています。
System.out.println("Length is 5");
List<String> result1 = months.stream().filter(lengthEqualWith(5)).collect(Collectors.toList());
result1.forEach(System.out::println);
System.out.println("Length is 8");
List<String> result2 = months.stream().filter(lengthEqualWith(8)).collect(Collectors.toList());
result2.forEach(System.out::println);
以下のような(期待通りの)結果が得られます。
Length is 5
March
April
Length is 8
February
November
December
このラムダ式は expectsLength 変数をスコープ内で探しますが、このスコープのことを静的スコープ(または構文スコープ)と呼ばれるようです。
expectsLength 変数がスコープ内に存在(キャッシュ)していれば、ラムダ式はその値を使います。
今回の例では、lengthEqualWith() メソッド内部がその静的スコープになります。
ラムダ式は final のローカル変数にしかアクセスできないため、expectsLength 変数は final である必要があります。
たとえ、final と宣言されていなくとも、final である条件が満たされていれば(つまり、その変数が初期化されており、不変であれば)動作するようです。
ただ、Java としてはそれらの条件が満たされているかを評価する必要がある(コストがかかる)ため、こういった値をキャッシュするのとしないのでは、パフォーマンスに若干の違いが出るかもしれません。