ラムダ式の導入のおかげでリスト処理が本当に楽になりました。
1. ラムダ式の書き方
ラムダ式というのは、要するに関数のことです。関数とは、一定の入力を受けて一定の出力を行うようなものです。めちゃくちゃ雑な表現をすると、関数を表現する一般的な型がJava8において導入され、かつそれを記述するための構文が導入されたということになります。
1-1. 基本的な書き方
基本的には、(型 変数名) -> { 処理 }
という書き方になります。
Consumer<String> func = (String input) -> { System.out.println(input); };
この基本的な書き方を古典的に書くと以下のようになります。
Consumer<String> func = new Consumer<String>() { @Override public void accept(String input) { System.out.println(input); } };
こうするとわかりやすいですね。
1-2. 省略
ラムダ式の記述法は、型やブラケットが省略できるので、とてもすっきりした書き方ができます。要するに型推論してくれているということですかね。
Consumer<String> func2 = input -> System.out.println(input);
また、メソッド参照という構文が追加されたので、それを用いるとすさまじいことになります。
Consumer<String> func3 = System.out :: println;
あらすっきり。
2. リスト処理
さて、ラムダ式を用いると何が楽しいのかというと、複数の要素に対して同一の処理を適用する場面、すなわちリスト処理の記述がかなり楽になります。以下にリスト処理の基本を列挙しましょう。
2-1. 単純な繰り返し処理(forEach)
一番簡単なリスト処理メソッドforEach
はListから利用できます。
List<String> lines = new ArrayList<String>() {{ this.add("hoge"); this.add("fuga"); this.add("foo"); this.add("bar"); }}; lines.forEach(System.out :: println);
・結果
hoge fuga foo bar
2-2. 比較的高度なリスト処理
forEachよりも高度なリスト処理を利用するにはStreamクラスを利用します。Listはstream
メソッドによって容易にStreamクラスに変換することができます。また、以下のリスト処理メソッドが受け取る引数はConsumeクラスではないので、その点も注意です。(といってもクラス名を明示的に指示しなければならない場面でもなければあまり問題にならなさそうですが)
// 写像(map) lines.stream().map(line -> "-" + line); // 集約(reduce) lines.stream().reduce((l1, l2) -> l1 + System.lineSeparator() + l2); // 絞り込み(filter) lines.stream().filter(line -> line.length == 4);
map
は与えられた関数を用いて各要素を変換します。ちょうど集合論でいう写像にあたる操作です。上記の例は、リストの各要素に対して先頭に”-“を追加する処理をしています。したがって、戻り値は例えば”-hoge”や”-fuga”になっています。
なお、mapを利用した場合の戻り値の型はStream
reduce
は与えられた関数を用いて要素を集約します。上記の例は、すべての要素を改行区切りで一つのString値にまとめています。
reduceの結果はOptionalget()
メソッドを呼んでやる必要があります。
filter
は与えられた関数を用いて要素の絞り込みを行います。上記の例は、要素の文字列の中からちょうど文字列長が4の要素だけを抽出する処理になります。つまり、”hoge”, “fuga”は残りますが、”foo”, “bar”は結果から排除されます。これも戻り値の型がStream
まとめ
Java7までの冗長な構文から、非常にすっきりして簡単な記述になりましたね。これでロジックに集中して実装ができるので、思考経済の点ではかなり良いのではないでしょうか。