Posted at

コレクションを強化したStream

More than 5 years have passed since last update.


Streamでなんでもできちゃうんじゃないかな

java8にはコレクションを処理するためにStreamという仕組みが新たに取り入れられました。javaにはjava5で取り入れられた拡張for文がありますが、そんなの比較にならないくらい便利になりました。

まずは基本のforEachから。


ForEach.java

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以外にも色々なメソッドが実装されています。ここでは例としてmapToIntfilterを使ってみます。


Sum.java

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型なので、mapToIntint xxx(String s)という関数を引数に受け取ります。mapToIntはstreamの要素1つ1つに引数で受け取った関数を適用していきます。つまり数字文字列のリストが数値リストに変換されます。

次にfilterが実行されます。filterはstreamの要素を受け取ってbooleanを返す関数を受け取ります。そして関数の結果がtrueである要素だけを収集します。この例では奇数だけが収集されて、2は捨てられます。

最後にstreamの要素すべてを加算します。

結果は4になります。

絵で書くとこんな感じです。


Sum2.java

元のリスト           mapToInt    filter    sum

["1", "2", "3"] -> [1, 2, 3] -> [1, 3] -> 4

これと同じ処理をjava7で書くと


java.SumJava7.java

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つの要素に集約する

これらは「この中から最大値を取得する」みたいな大枠の動作しか提供しません。コレクションの中で「何を最大値とするか?」といった実際の処理は引数で渡します。実際の処理を外部から与えられるので、すごく柔軟に大きな処理を組み立てられるようになっています。