Streamでなんでもできちゃうんじゃないかな
java8にはコレクションを処理するためにStreamという仕組みが新たに取り入れられました。javaにはjava5で取り入れられた拡張for文がありますが、そんなの比較にならないくらい便利になりました。
まずは基本のforEach
から。
Arrays.asList("a", "b", "c").stream().forEach(s -> System.out.println(s));
Listにstream()
というメソッドが追加されました。これはリストに対するStreamを返すメソッドで、Listにdefaultで実装されています。
forEachはStreamの全要素に対して引数の関数を適用しています。引数の無名関数はprintln
を呼ぶだけのラッパーで、実行するとコンソールにa b c
と表示されます。
さて、これだけだと何も便利になった感じがしないですね。むしろ拡張for文の方がわかりやすいです。
java.util.stream.Stream
StreamインターフェイスにはforEach以外にも色々なメソッドが実装されています。ここでは例としてmapToInt
とfilter
を使ってみます。
List<String> nums = asList("1", "2", "3");
int sum = nums.stream()
.mapToInt(Integer::parseInt)
.filter(i -> i % 2 == 1)
.sum();
mapToIntはstreamの要素を引数にしてintを返す関数を引数に取ります。この例だと、streamはString型なので、mapToInt
はint xxx(String s)
という関数を引数に受け取ります。mapToInt
はstreamの要素1つ1つに引数で受け取った関数を適用していきます。つまり数字文字列のリストが数値リストに変換されます。
次にfilter
が実行されます。filterはstreamの要素を受け取ってbooleanを返す関数を受け取ります。そして関数の結果がtrue
である要素だけを収集します。この例では奇数だけが収集されて、2
は捨てられます。
最後にstreamの要素すべてを加算します。
結果は4になります。
絵で書くとこんな感じです。
元のリスト mapToInt filter sum
["1", "2", "3"] -> [1, 2, 3] -> [1, 3] -> 4
これと同じ処理をjava7で書くと
int total = 0;
for (String i : nums) {
int num = Integer.parseInt(i);
if (num % 2 == 1) {
total += num;
}
}
こんな感じになります。
java8では「数値に変換して」「奇数を集めて」「足し込む」とここの小さい処理をくっつけて大きな処理を作る事ができます。
Streamには他にも以下のようなメソッドが用意されています。
メソッド | 動き |
---|---|
allMatch | streamの全要素が指定の条件(関数)を満たすかどうかをチェックする |
anyMatch | streamの要素のうち1つでも指定の条件(関数)を満たすかどうかをチェックする |
concat | 2つのstreamを結合する |
count | List.size()的なもの |
distinct | equalsを使用して、streamから重複データを省く |
filter | 指定した条件に合致する要素のみを収集する |
map | 要素を別の値に変換する関数A->Bを渡してStream<A>からStream<B>を作成する |
forEach | 指定した関数を全要素に対して実行していく |
max, min | 比較関数を渡して、要素内の最大値や最小値を取得する |
reduce | 2つの要素を1つにまとめる関数を引数にとって、全要素を1つの要素に集約する |
これらは「この中から最大値を取得する」みたいな大枠の動作しか提供しません。コレクションの中で「何を最大値とするか?」といった実際の処理は引数で渡します。実際の処理を外部から与えられるので、すごく柔軟に大きな処理を組み立てられるようになっています。