StreamAPIについて調べてみた collect編 その1
【前提条件】
【概要】
前回はStreamAPIの使い方について調べました。
今回はStreamAPIのメソッドのうち、
collectを調べてみました。
collectはStreamAPIで実行した結果を
集計する機能です。
【Collectors】
Collectorsクラスはよく使用する機能がまとめられたクラスです。
toListメソッド、toSetメソッドなど結果をコレクションに変換する機能。
joiningメソッド、maxByなど集計した結果を加工する機能。
よく使われるであろう機能はまとめられています。
collecを使う場合には基本的にCollectorsを使うことになると思います。
前回のサンプルとほぼ同じですが、
streamを作ってcollectするソースコードを見てみます。
public static void main(final String[] args) { final List<String> list = new ArrayList<>(); list.add("two"); list.add("one"); list.add("zero"); list.add("three"); final List<String> convertList = list.stream().collect(Collectors.toList()); convertList.forEach(System.out::println); }
Collectors#toListで結果をListに変換します。
サンプルでは何もせずListにしているので無意味なソースですが。
【独自のCollectorクラス】
[Stream#collectメソッド]
collecctメソッドの定義を見てみましょう。
public interface Stream<T> extends BaseStream<T, Stream<T>> { <R, A> R collect(Collector<? super T, A, R> collector); }
TはStreamを作成したコレクションの型です。
サンプルではStringです。
Aはアキュムレータの型を指定します。
アキュムレータは大雑把に説明すると途中結果を保持するためものです。
Rは戻り値の方を指定します。
パラメータにはCollectorインターフェイスのオブジェクトを受け取ります。
[Collector]
Collectorの動きを理解するために
文字列を数値に変換してSetで返すCollectorを作成します。
import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; public class SampleCollector implements Collector<String, List<Integer>, Set<Integer>> { @Override public Supplier<List<Integer>> supplier() { return () -> new ArrayList<>(); } @Override public BiConsumer<List<Integer>, String> accumulator() { return (acumList, value) -> { switch(value) { case "one" : acumList.add(1); break; case "two" : acumList.add(2); break; case "three" : acumList.add(3); break; default: acumList.add(0); } }; } @Override public BinaryOperator<List<Integer>> combiner() { return (returnList, colletedList) -> { System.out.println("combiner!"); returnList.addAll(colletedList); return returnList; }; } @Override public Function<List<Integer>, Set<Integer>> finisher() { return paramList -> { final Set<Integer> returnSet = new LinkedHashSet<>(); returnSet.addAll(paramList); returnSet.add(10); return returnSet; }; } @Override public Set<Collector.Characteristics> characteristics() { final Set<Collector.Characteristics> returnSet = new HashSet<>(); // 3つの指定ができますが、サンプルではOFFです。 // returnSet.add(Characteristics.IDENTITY_FINISH); // returnSet.add(Characteristics.CONCURRENT); // returnSet.add(Characteristics.UNORDERED); return returnSet; } }
supplierメソッドではSupplierインターフェイスのオブジェクトを返します。
supplierではアキュムレータの初期状態を作ります。
Supplierインターフェイスはパラメータなし、戻り値ありの関数型インターフェイスです。
accumulatorメソッドではコレクション内の各要素に対する処理を実装します。
パラメータはアキュムレータと各要素の値です。
サンプルでは文字列を数値に変換してアキュムレータのListにつめています。
combinerメソッドでは並列に処理を行ったときに実行される処理です。
finisherメソッドは最終的に実行される処理です。
パラメータとしてアキュムレータが渡されます。
サンプルではアキュムレータで渡されたListをSetに変換して、最後に10をつめています。
characteristicsメソッドはCollectorオブジェクトの特性を指定します。
[characteristics]
characteristicsメソッドで指定した値で↓のように動きが変わります。
IDENTITY_FINISHを設定するとfinisherメソッドが実行されなくなります。
finisherメソッドが実行されない代わりに、
最終的なアキュムレータが返却されるようになります。
今回のサンプルでこのオプションを指定すると戻り値はListになってしまいます。
アキュムレータと戻り値の型が異なる場合は指定してはだめなようです。
CONCURRENTとUNORDEREDを指定した場合、
並行に処理した時に処理順序が保障されなくなります。
↑のオプションを指定すると実行するたびに
結果も変わっているっぽいのはサンプルが悪いのかもしれません・・・
【呼び出し元】
では、作成したSampleCollectorクラスを使ってみます。
[ソース]
public static void main(final String[] args) { final List<String> list = new ArrayList<>(); list.add("two"); list.add("one"); list.add("zero"); list.add("three"); SampleCollector sampleCollector = new SampleCollector(); final Set<Integer> collectorList = list.stream().collect(sampleCollector); collectorList.forEach(System.out::println); System.out.println("================ parallel ================"); final Set<Integer> parallelList = list.stream().parallel().collect(sampleCollector); parallelList.forEach(System.out::println); }
[実行結果]
2 1 0 3 10 ================ parallel ================ combiner! combiner! combiner! 2 1 0 3 10
結果を見るとfinisherメソッドで追加した10が表示されているのがわかります。
また、並行処理(Stream#paralellを実行したもの)にした場合、
conbinerが呼び出されていることもわかります。
【まとめ】
今回はcollectメソッドで行われていることを調べてみました。
いろいろ書いたのですが基本的にはCollectorsクラス内に
定義されているメソッドを使用すると思います。