今回から何回かに分けて java.util.stream パッケージに定義されている Collectors クラスに定義されているメソッドを見ていきます。
目次
- Map 以外を返す Collector
- Map を返す Collector
- 内蔵コレクタの特性
- parallel ストリーム と concurrent コレクタ
- 独自コレクタを作ってみよう!
- 独自コレクタを作る必要はない!?
Collector インターフェースと Collectors クラス
Collectors クラスはいわゆるユーティリティ・クラスで、インスタンス生成やクラス拡張はできず、static メソッドのみが定義されています。 これらの static メソッドはほとんどが java.util.stream.Collector オブジェクトを返すメソッドで*1、よく使うであろうコレクターが定義されてます。Collector インターフェースは Stream インターフェースの collect() メソッドに引数として渡して使います。 Collector インターフェースは型パラメータが2つ宣言されていて
package java.util.stream; public interface Collector<T,R>{ ... }
T はストリームの要素の型(つまり Stream<T> オブジェクトの collect() メソッドに渡せる)、R は collect() メソッドの返り値の型になります。 今回は返り値が Map でないコレクターを見ていきます:
今回扱うメソッドは以下のもの:
// 汎用コレクター static <T,U,R> Collector<T,R> mapping(Function<? super T,? extends U> mapper, Collector<? super U,R> downstream) static <T> Collector<T,T> reducing(BinaryOperator<T> op) static <T> Collector<T,T> reducing(T identity, BinaryOperator<T> op) static <T,U> Collector<T,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op) // 文字列への変換 static Collector<String,StringBuilder> toStringBuilder() static Collector<CharSequence,StringJoiner> toStringJoiner(CharSequence delimiter) // コレクションへの変換 static <T,C extends Collection<T>> Collector<T,C> toCollection(Supplier<C> collectionFactory) static <T> Collector<T,List<T>> toList() static <T> Collector<T,Set<T>> toSet() // 要約統計量 Summary Statistics static <T> Collector<T,Long> counting() static <T> Collector<T,Long> sumBy(Function<? super T,Long> mapper) static <T> Collector<T,T> maxBy(Comparator<? super T> comparator) static <T> Collector<T,T> minBy(Comparator<? super T> comparator) static <T> Collector<T,IntSummaryStatistics> toIntSummaryStatistics(ToIntFunction<? super T> mapper) static <T> Collector<T,LongSummaryStatistics> toLongSummaryStatistics(ToLongFunction<? super T> mapper) static <T> Collector<T,DoubleSummaryStatistics> toDoubleSummaryStatistics(ToDoubleFunction<? super T> mapper)
引数の型の型パラメータまで見てると億劫になるので、とりあえず返り値の Collector の第2型パラメータが、Stream#collect() メソッドに Collector を渡した後の返り値だっ、てところをチェックしておけばいいんじゃないかなぁ。
汎用コレクター
まずはある程度汎用的に使える Collector を返すメソッド:static <T,U,R> Collector<T,R> mapping(Function<? super T,? extends U> mapper, Collector<? super U,R> downstream) static <T> Collector<T,T> reducing(BinaryOperator<T> op) static <T> Collector<T,T> reducing(T identity, BinaryOperator<T> op) static <T,U> Collector<T,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
Stream インターフェースに定義されている map() や reduce() と何が違うの?という気もしますが、JavaDoc を見ていると、次回やる groupingBy(), partitioningBy() メソッドのうち Collector オブジェクトを引数にとるものに対して使うといいようです。 まぁ、今回はあまり気にせずに普通に使ってみましょう:
import static java.util.stream.Collectors.*; // mapping() 各文字列を、その長さの int 値に変換 Stream<String> stream0 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); List<Integer> lengthList = stream0.collect(mapping(s -> s.length(), toList())); assert lengthList.get(2) == "Scala".length(); // reducing() その1 文字列を連結 Stream<String> stream1 = Stream.of("Java", "Groovy", "Scala", "Clojure"); assertString(stream1.collect(reducing((s0, s1) -> s0 + s1)), "JavaGroovyScalaClojure"); // reducing() その2 Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure"); assertString(stream2.collect(reducing("*", (s0, s1) -> s0 + s1)), "*JavaGroovyScalaClojure"); // reducing() その3 Stream<String> stream3 = Stream.of("Java", "Groovy", "Scala", "Clojure"); assert stream3.collect(reducing(0, s -> s.length(), (i, j) -> i + j)) == 22; /** 文字列のアサーション */ public static void assertString(String s0, String s1){ assert s0.equals(s1); }
reducing() のうち、1つ目、2つ目のものはストリームの要素の型と返り値の型が同じになってます。 また、引数が BinaryOperator<T> オブジェクトを1つだけとる reducing() の返り値は T ですが、Stream<T> インターフェースの reduce() メソッドは Optional<T> であるという違いもあります。
文字列への変換
次は文字列に関するコレクターを返すメソッド:static Collector<String,StringBuilder> toStringBuilder() static Collector<CharSequence,StringJoiner> toStringJoiner(CharSequence delimiter)
java.util.StringJoiner クラスは Java 8 から導入された新たなクラスで、複数の文字列 (CharSequence) を指定した文字列(デリミター)で連結して返すメソッドです。 使い方はどちらも同じようなものですが、返り値が String オブジェクトではないことに注意:
import static java.util.stream.Collectors.*; // StringBuilder を構築 Stream<String> stream4 = Stream.of("Java", "Groovy", "Scala", "Clojure"); assertString(stream4.collect(toStringBuilder()).toString(), "JavaGroovyScalaClojure"); // StringJoiner を構築 Stream<String> stream5 = Stream.of("Java", "Groovy", "Scala", "Clojure"); assertString(stream5.collect(toStringJoiner(", ")).toString(), "Java, Groovy, Scala, Clojure); /** 文字列のアサーション */ public static void assertString(String s0, String s1){ assert s0.equals(s1); }
たぶん StringJoiner を使うコレクターは結構デバッグなので多用することになるかと。
コレクションへの変換
次はストリームをコレクションに変換するコレクター。 List, Set に変換するコレクターと、具象クラスを指定してコレクションを構築するコレクターがあります:static <T,C extends Collection<T>> Collector<T,C> toCollection(Supplier<C> collectionFactory) static <T> Collector<T,List<T>> toList() static <T> Collector<T,Set<T>> toSet()
使い方は一番簡単かと:
import static java.util.stream.Collectors.*; // Collection の構築 (TreeSet) Stream<String> stream6 = Stream.of("Java", "Groovy", "Scala", "Clojure"); NavigableSet<String> naviset = stream6.collect(toCollection(TreeSet::new)); assert naviset instanceof NavigableSet; assertString(naviset.stream().collect(toStringJoiner("")), "ClojureGroovyJavaScala"); // List の構築 Stream<String> stream7 = Stream.of("Java", "Groovy", "Scala", "Clojure"); List<String> list = stream7.collect(toList()); assert list.size() == 4; assert list instanceof ArrayList; // Set の構築 Stream<String> stream8 = Stream.of("Java", "Groovy", "Groovy", "Scala", "Java", "Clojure", "Java"); Set<String> set = stream8.collect(toSet()); assert set.size() == 4; assert set instanceof HashSet; set.forEach(e -> { assert stream().anyMatch(f -> e.equals(f)); }); /** 文字列のアサーション */ public static void assertString(Object s0, String s1){ assert s0.toString().equals(s1); }
まぁ、List と Set で大抵はなんとかなるかな。 toList(), toSet() で生成されるオブジェクトの型をアサーションしてますが、これは実装によるかと。
要約統計量
要約統計量(summary statistics 代表値)は和とか平均とかのことですが、プリミティブ型ストリームの記事で同様のメソッドを扱いましたね。 ここでは同じことを、数値に変換する方法や Comparator オブジェクトとともに指定する感じです。 counting() は単に要素の個数を数えるだけですが:static <T> Collector<T,Long> counting() static <T> Collector<T,Long> sumBy(Function<? super T,Long> mapper) static <T> Collector<T,T> maxBy(Comparator<? super T> comparator) static <T> Collector<T,T> minBy(Comparator<? super T> comparator) static <T> Collector<T,IntSummaryStatistics> toIntSummaryStatistics(ToIntFunction<? super T> mapper) static <T> Collector<T,LongSummaryStatistics> toLongSummaryStatistics(ToLongFunction<? super T> mapper) static <T> Collector<T,DoubleSummaryStatistics> toDoubleSummaryStatistics(ToDoubleFunction<? super T> mapper)
使い方もプリミティブ型ストリームのときと同じです:
import static java.util.stream.Collectors.*; // counting() Stream<String> stream9 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); assert stream9.collect(counting()) == 7; // sumBy() Stream<String> stream10 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); assert stream10.collect(sumBy(s -> Long.valueOf(s.length()))) == 39; // maxBy() Stream<String> stream11 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); assertString(stream11.collect(maxBy((s0, s1) -> s0.length() - s1.length())), "Clojure"); // IntSummaryStatistics の構築 Stream<String> stream12 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); IntSummaryStatistics stat = stream12.collect(toIntSummaryStatistics(s -> s.length())); assert stat.getCount() == 7; assert stat.getSum() == 39; assertDouble(stat.getAverage(), 39.0/7.0);
次回は返り値が Map のコレクターを見ていきます。

- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (46件) を見る
*1:次回扱う、merger をあらわす BinaryOperator を返すメソッド以外。