Java の Collectors クラスに定義されているメソッドを見ていくシリーズ(目次)。 前回、簡単な独自コレクタを作ってみましたが、作ったコレクタをあちこちで使い回したりしないなら、ラムダ式と Stream インターフェースに定義されているメソッドで事足りるようです。
コレクタの種類は大別して「値を返す」ものと「コンテナ・オブジェクトを返す」ものがあることを前回見ましたが、それぞれ
- 値を返す・・・Stream#reduce() メソッド
- コンテナ・オブジェクト・・・Stream#collect() メソッド
を使えば同様の機能を簡単に使うことができます。 もちろん、コレクタの accumulator や combiner が簡単な(1行の)ラムダ式で書けないとか、いろいろな場所で同様のコレクタを使うとかなら、クラスとして抽出するのも OK ですが。
値を返すコレクタ ・・・reduce() メソッド
値を返すコレクタとして、文字列のストリームから全文字数を計算するコレクタを作ってみましょう。public static class StringLengthCollector implements Collector<String, Integer>{ public Set<Collector.Characteristics> characteristics(){ return Collections.emptySet(); } public Supplier<Integer> resultSupplier(){ return () -> 0; } public BiFunction<Integer, String, Integer> accumulator(){ return (i, s) -> i + s.length(); // return (i, s) -> { System.out.println(s); return i + s.length(); }; } public BinaryOperator<Integer> combiner(){ return (i, j) -> i + j; //return (i, j) -> { System.out.println("combine!"); return i + j; }; } }
これを使うには
Stream<String> stream0 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); assert stream0.collect(new StringLengthCollector()) == 39; Stream<String> stream1 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby").parallel(); assert stream1.collect(new StringLengthCollector()) == 39;
これを reduce() メソッドを使って書いてみましょう。 使う reduce() のシグニチャは
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
です。 Collector#resultSupplier() の代わりに reduce() メソッドの第1引数に初期値を渡す以外は同じです:
Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); assert stream2.reduce(0, (i, s) -> i + s.length(), (i, j) -> i + j) == 39; Stream<String> stream3 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby").parallel(); assert stream3.reduce(0, (i, s) -> i + s.length(), (i, j) -> i + j) == 39;
コンテナ・オブジェクトを返すコレクタ・・・collect() メソッド
コンテナ・オブジェクトを返すコレクタは前回作った、StringJoinerCollector を使い回しましょう。public class StringJoinerCollector implements Collector<String, StringJoiner>{ public Set<Collector.Characteristics> characteristics(){ return EnumSet.of(STRICTLY_MUTATIVE); } public Supplier<StringJoiner> resultSupplier(){ return () -> new StringJoiner(", "); } public BiFunction<StringJoiner, String, StringJoiner> accumulator(){ return (sj, s) -> sj.add(s); } public BinaryOperator<StringJoiner> combiner(){ return (sj0, sj1) -> sj0.add(sj1); } }
これを使うには
Stream<String> stream4 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); System.out.println( stream4.collect(new StringJoinerCollector())); // 「Java, Groovy, Scala, Clojure, Kotlin, Jython, JRuby」と表示 Stream<String> stream5 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby").parallel(); System.out.println( stream5.collect(new StringJoinerCollector())); // 「Java, Groovy, Scala, Clojure, Kotlin, Jython, JRuby」と表示
これを collect() メソッドを使って書いてみましょう。 使う collect() のシグニチャは
<R> R collect(Supplier<R> resultFactory,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
です。 コンテナ・オブジェクトを返す場合、accumulator や combiner が返り値を返す必要がない(第1引数が collect() メソッドの返り値のコンテナ・オブジェクトになる)ので、collect() メソッドの引数が BiFunction, BinaryOperator から BiConsumer に変更されています。 まぁ、ラムダ式で書く場合はあんまり違いを意識する必要はありませんが。 上記のサンプルを collect() メソッドで書くと以下のようになります:
Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"); System.out.println( stream2.collect(() -> new StringJoiner(", "), (sj, s) -> sj.add(s), (sj0, sj1) -> sj0.add(sj1.toString()))); // 「Java, Groovy, Scala, Clojure, Kotlin, Jython, JRuby」と表示 Stream<String> stream3 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby").parallel(); System.out.println( stream2.collect(() -> new StringJoiner(", "), (sj, s) -> sj.add(s), (sj0, sj1) -> sj0.add(sj1.toString()))); // 「Java, Groovy, Scala, Clojure, Kotlin, Jython, JRuby」と表示
reduce(), collect() メソッド、どちらで書き換える場合もラムダ式を複数書かないといけないので、複雑になりそうならコレクタをクラスとして抽出する方が無難かもしれませんね。 あと、今回はコレクタの特性はあまり気にしていませんでしたが、並列実行やパフォーマンスなどの点から特性を指定する必要があるならコレクタ・クラスを実装すべきでしょう。
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (155件) を見る
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (46件) を見る