collect で集める
Stream のままでは、次の行で使えない
filter で絞り込んだ結果を、そのまま返そうとするとコンパイルが通りません。
Java
List<String> actives = members.stream().filter(m -> m.isActive()); // 型が合わないfilter が返すのは Stream<String> であって List<String> ではないからです。Stream は流れている途中の状態で、そのままでは size() も呼べませんし、for で回すこともできません。流し終わったものを、使える入れ物に詰め直す必要があります。その役目が collect です。
Java
List<String> actives = members.stream()
.filter(m -> m.isActive())
.collect(Collectors.toList());collect は終端操作なので、ここで Stream は終わりです。toList() と書ける場面も多いですが、詰め方を細かく指定したいときは Collectors の出番になります。
詰め方は Collectors から選ぶ
collect の引数には「どう詰めるか」を渡します。これを毎回自分で書く必要はなく、java.util.stream.Collectors によく使うものが用意されています。
リストではなく 1 本の文字列にまとめたいときは joining です。
Java
String label = names.stream()
.collect(Collectors.joining(" / "));
// 佐藤 / 鈴木 / 田中区切り文字を渡すと、要素の間にだけ挟んでくれます。末尾には付きません。要素が 1 件なら区切りは 1 つも入らず、要素が 0 件なら空文字列が返ります。自分で最後のカンマを消す処理は要りません。
キーと値の組にしたいときは toMap です。1 つ目の式がキー、2 つ目の式が値を作ります。
Java
Map<String, Integer> lengthOf = names.stream()
.collect(Collectors.toMap(n -> n, n -> n.length()));キーがぶつかると例外で落ちる
toMap で気をつけたいのが、キーが重複したときです。上の例で names に同じ名前が 2 回入っていると、IllegalStateException が出て処理が止まります。
Map にキーは 1 つしか置けないので、後から来た値で上書きするのか、先に入っていた値を残すのか、Java が勝手に決めるわけにはいきません。決め方を渡していないので、黙って片方を捨てる代わりに例外にしている、という作りです。同じキーが出うるデータを扱うときは、先に重複を落としておくか、3 つ目の引数で「ぶつかったらどちらを採るか」を渡します。
joiningはStream<String>にしか使えません。数値の Stream から直接は呼べないので、先に文字列の Stream に変えておく必要があります。
やってみよう
Solution.evensJoined(int[] arr) を完成させて、配列の中の偶数だけを , 区切りでつないだ文字列を返してください。
Arrays.stream(arr) で IntStream を作り、filter で偶数だけ残すところまでは前の回と同じです。問題はその先で、数値の Stream のままでは Collectors に渡せません。mapToObj(String::valueOf) で 1 件ずつ文字列に変えて Stream<String> にしてから、collect に Collectors.joining(",") を渡します。
偶数が 1 つも無いときと、空配列のときは、どちらも空文字列が返ります。区切り文字の付け方も件数の判定も joining に任せられるので、自分で if を書く場面はありません。
要件
Arrays.stream(arr)でIntStreamを作ることfilterで偶数 (n % 2 == 0) だけ残すことmapToObj(String::valueOf)でStream<String>に変換してからcollect(Collectors.joining(","))で連結すること
入出力例
evensJoined([1,2,3,4,5,6]) → "2,4,6"
evensJoined([1,3,5]) → ""
evensJoined([]) → ""
evensJoined([2]) → "2"
evensJoined([-4,-3,-2,-1,0]) → "-4,-2,0"