Collectors#filteringが欲しくなる件
以前,@cero_tさんが書いたブログで「部署をキーにしたMapを作ったうえで、Mapの値のほうには給与が1000を超える社員の人数を入れています。フィルタ処理を入れたいがために、ただの集計処理が使えず、Streamを利用しています。」ということがあって,それなりに綺麗に書ける方法を提案しました.
大本がこのコード.
Map<Dept, Long> groupByDeptAndFilter2(List<Emp> list) { return list.stream() .collect(Collectors.groupingBy(emp -> emp.dept)) .entrySet() .stream() .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue() .stream() .filter(emp -> emp.sal > 1000) .count())); }
で,提案したコードが以下.
Map<Dept, Long> groupByDeptAndFilter1(List<Emp> list) { return list.stream() .collect(Collectors.groupingBy(emp -> emp.dept, Collectors.collectingAndThen(Collectors.toList(), emps -> emps.stream() .filter(e -> e.sal > 1000) .count()))); }
提案したコードのほうが短くなっていて,比較的シンプルなのだけど,どちらのコードも一回Listを作ってる辺りで,凄くダサいし,メモリとか性能的にもよくない.
そもそもフィルタリングしたいので,Collectors#mappingのようにfilteringしたCollectorを作ってくれる奴がいればもっとクールに書けるのではという話.
そこでfilteringを以下のように定義する.
public static <T, A, R> Collector<T, ?, R> filtering(Predicate<? super T> predicate, Collector<? super T, A, R> collector) { BiConsumer<A, ? super T> downstream = collector.accumulator(); return Collector.<T, A, R>of(collector.supplier(), (a, t) -> {if (predicate.test(t)) downstream.accept(a, t);}, collector.combiner(), collector.finisher(), collector.characteristics().stream().toArray(Collector.Characteristics[]::new)); }
条件にあった時にしかaccumulateしないようにしたCollectorを生成している.
今回の場合はこのfilteringをcountingを用いると以下のように書ける.
Map<Dept, Long> groupByDeptAndFilter(List<Emp> list) { return list.stream() .collect(groupingBy(emp -> emp.dept, filtering(emp -> emp.sal > 1000, counting()))); }
何気にこれが正解な気がする.
http://ideone.com/83Lzb7
問題はfilteringがCollectorsにないっていうこと.
誰かOpenJDKに提案して←
提案してみた
雰囲気良さそう
行けちゃう.
現状ではこれが一番よさ気
Map<Dept, Long> groupByDeptAndFilter(List<Emp> list) { return list.stream() .collect(toMap(e -> e.dept, e -> e.sal > 1000 ? 1L : 0L, Long::sum)); }
filteringを提案してしまったけど,どうしようw
filtering,マージされた.