JavaのStreamのcollectメソッドで使われるCollectorインターフェースおよびCollectorsクラスについて。
|
Streamの処理結果を他のクラスに変換するには、Stream#collectメソッドを使う。
(sumやreduce・forEachといった終端処理メソッドも、内部ではcollectと同様の処理を行っている)
Stream#collect(Collector)メソッドにはCollectorオブジェクトを渡す。
CollectorインターフェースはStream#collect(supplier,
accumulator, combiner)メソッドの各引数に渡す関数を1つにまとめたもの。
Collectorを自分で作ることも出来るが、よく使いそうなものはCollectorsクラスにメソッドが用意されている。
Collectorインターフェースには型引数が3つ付いている。
public interface Collector<T, A, R> { 〜 }
型引数 | 説明 |
---|---|
T | 対象となるStreamの要素(値)の型 |
A | 結果生成に使うクラス(実装都合で決めるものなので、外部からはあまり重要ではない) |
R | 生成する結果の値の型 |
例えば「Collector<String, ?,
Integer>
」だと、Stream<String>
からInteger
を生成する。
例えば「Collector<String, ?,
List<String>>
」だと、Stream<String>
からList<String>
を生成する。
Collectorsクラスには、Stream#collect(Collector)メソッドに渡すCollectorインターフェースの具象クラス(を返すメソッド)が色々用意されている。
import java.util.stream.Collectors;
|
|
メソッド | ver | 戻り型 | 引数 | 説明 | 例 | Scala相当 | ||
---|---|---|---|---|---|---|---|---|
コーディング | 結果 | |||||||
toCollection |
Collector<T, ?, C> |
() -> C |
collectionFactory |
コレクションに変換する。 コレクションインスタンスを生成する関数を渡す。 |
Stream<String> s = Stream.of("a", "b", "c"); |
[a, b, c] |
||
toList |
Collector<T, ?, List<T>> |
Listに変換する。 | Stream<String> s = Stream.of("a", "b", "c"); |
[a, b, c] |
val s = Stream("a", "b", "c") |
|||
toUnmodifiableList |
10 | Collector<T, ?, List<T>> |
不変リストに変換する。[2018-06-02] | |||||
toSet |
Collector<T, ?, Set<T>> |
Setに変換する。 | Stream<String> s = Stream.of("a", "b", "c"); |
[a, b, c] |
val s = Stream("a", "b", "c") |
|||
toUnmodifiableSet |
10 | Collector<T, ?, Set<T>> |
不変Setに変換する。[2018-06-02] | |||||
joining |
Collector<CharSequence, ?, String> |
結合してStringにする。 →StringJoiner |
Stream<String> s = Stream.of("a", "b", "c"); |
abc |
val s = Stream("a", "b", "c") |
|||
joining |
Collector<CharSequence, ?, String> |
CharSequence |
delimiter |
結合してStringにする。 区切り文字を指定する。 →StringJoiner |
Stream<String> s = Stream.of("a", "b", "c"); |
a:b:c |
val s = Stream("a", "b", "c") |
|
joining |
Collector<CharSequence, ?, String> |
CharSequence |
delimiter |
結合してStringにする。 区切り文字・前後文字を指定する。 →StringJoiner |
Stream<String> s = Stream.of("a",
"b", "c"); |
<a:b:c> |
val s = Stream("a", "b", "c") |
|
CharSequence |
prefix |
|||||||
CharSequence |
suffix |
|||||||
mapping |
Collector<T, ?, R> |
(T) -> U |
mapper |
値を変換して他のコレクターを呼び出す。 | Stream<Character> s = Stream.of('a',
'b', 'c'); |
a:b:c |
||
Collector<U, A, R> |
downstream |
|||||||
flatMapping |
9 | Collector<T, ?, R> |
(T) -> Stream<U> |
mapper |
値をStreamに変換して他のコレクターを呼び出す。[2017-09-24] | Stream<String> s = Stream.of("abc", "bcd", "zz"); |
{2=[z], 3=[a, b, c, d]} |
|
Collector<U, A, R> |
downstream |
|||||||
filtering |
9 | Collector<T, ?, R> |
(T) -> boolean |
predicate |
条件を満たす値だけ他のコレクターに渡す。[2017-09-24] | Stream<String> s = Stream.of("abc", "bcd", "zz", "yy"); |
{2=[yy], 3=[abc, bcd]} |
|
Collector<U, A, R> |
downstream |
|||||||
collectingAndThen |
Collector<T, A, RR> |
Collector<T, A, R> |
downstream |
他のコレクターを呼び出した結果を変換する。 | Stream<String> s = Stream.of("a",
"b", "c"); |
[a, b, c] |
||
(R) -> RR |
finisher |
|||||||
counting |
Collector<T, ?, Long> |
要素数を取得する。 →Stream#count() |
Stream<String> s = Stream.of("a", "b", "c"); |
3 |
val s = Stream("a", "b", "c") |
|||
minBy |
Collector<T, ?, Optional<T>> |
Comparator<T> |
comparator |
最小値を取得する。 →Stream#min() |
Stream<String> s = Stream.of("a", "b", "c"); |
Optional[a] |
val s = Stream("a", "b", "c") |
|
maxBy |
Collector<T, ?, Optional<T>> |
Comparator<T> |
comparator |
最大値を取得する。 →Stream#max() |
Stream<String> s = Stream.of("a", "b", "c"); |
Optional[c] |
val s = Stream("a", "b", "c") |
|
summingInt |
Collector<T, ?, Integer> |
(T) -> int |
mapper |
各値をintに変換し、合算する。 →Stream#mapToInt()・IntStream#sum() →Collectors.summarizingInt() |
Stream<String> s = Stream.of("abc", "de", "f"); |
6 |
val s = Stream("abc", "de", "f") |
|
summingLong |
Collector<T, ?, Long> |
(T) -> long |
mapper |
各値をlongに変換し、合算する。 →Stream#mapToLong()・LongStream#sum() →Collectors.summarizingLong() |
Stream<String> s = Stream.of("100", "20", "3"); |
123 |
val s = Stream("100", "20", "3") |
|
summingDouble |
Collector<T, ?, Double> |
(T) -> double |
mapper |
各値をdoubleに変換し、合算する。 →Stream#mapToDouble()・DoubleStream#sum() →Collectors.summarizingDouble() |
Stream<String> s = Stream.of("1e2", "2e1", "3"); |
123.0 |
val s = Stream("1e2", "2e1", "3") |
|
averagingInt |
Collector<T, ?, Double> |
(T) -> int |
mapper |
各値をintに変換し、平均値を算出する。 空ストリームの場合は0を返す。 →Stream#mapToInt()・IntStream#average() →Collectors.summarizingInt() |
Stream<String> s = Stream.of("abc", "de", "f"); |
2.0 |
||
averagingLong |
Collector<T, ?, Double> |
(T) -> long |
mapper |
各値をlongに変換し、平均値を算出する。 空ストリームの場合は0を返す。 →Stream#mapToLong()・LongStream#average() →Collectors.summarizingLong() |
Stream<String> s = Stream.of("100", "20", "3"); |
41.0 |
||
averagingDouble |
Collector<T, ?, Double> |
(T) -> double |
mapper |
各値をdoubleに変換し、平均値を算出する。 空ストリームの場合は0を返す。 →Stream#mapToDouble()・DoubleStream#average() →Collectors.summarizingDouble() |
Stream<String> s = Stream.of("1e2", "2e1", "3"); |
41.0 |
||
reducing |
Collector<T, ?, T> |
T |
identity |
値を集約する。 →Stream#reduce() |
Stream<String> s = Stream.of("a",
"b", "c"); |
1abc |
val s = Stream("a", "b", "c") |
|
(T, T) -> T |
op |
|||||||
reducing |
Collector<T, ?, Optional<T>> |
(T, T) -> T |
op |
値集約する。 →Stream#reduce() |
Stream<String> s = Stream.of("a", "b", "c"); |
Optional[abc] |
val s = Stream("a", "b", "c") |
|
reducing |
Collector<T, ?, U> |
U |
identity |
値を集約する。 →Stream#reduce() |
||||
(T) -> U |
mapper |
|||||||
(U, U) -> U |
op |
|||||||
groupingBy |
Collector<T, ?, Map<K, List<T>>> |
(T) -> K |
classifier |
値をグループ分けする。 キーとなる値を取得する関数を渡す。 |
Stream<String> s = Stream.of("a", "bar", "c",
"foo", "zzz"); |
{1=[a, c], 3=[bar, foo, zzz]} |
val s = Stream("a", "bar", "c", "foo", "zzz") |
|
groupingBy |
Collector<T, ?, Map<K, D>> |
(T) -> K |
classifier |
値をグループ分けする。 グループ分けされた値群を集約する関数を渡す。 |
Stream<String> s = Stream.of("a",
"bar", "c", "foo"); |
{1=ac, 3=barfoo} |
val s = Stream("a", "bar", "c", "foo") |
|
Collector<T, A, D> |
downstream |
|||||||
groupingBy |
Collector<T, ?, M> |
(T) -> K |
classifier |
値をグループ分けする。 Mapのインスタンスを生成する関数を渡す。 |
Stream<String> s = Stream.of("a",
"bar", "c", "foo"); |
{1=ac, 3=barfoo} |
||
() -> M |
mapFactory |
|||||||
Collector<T, A, D> |
downstream |
|||||||
groupingByConcurrent |
Collector<T, ?, ConcurrentMap<K, List<T>>> |
(T) -> K |
classifier |
ConcurrentMapを利用したgroupingBy。 たぶん並列ストリームにおいてマルチスレッドで生成されるのだろう。 |
||||
groupingByConcurrent |
Collector<T, ?, ConcurrentMap<K, D>> |
(T) -> K |
classifier |
|||||
Collector<T, A, D> |
downstream |
|||||||
groupingByConcurrent |
Collector<T, ?, M> |
(T) -> K |
classifier |
|||||
() -> M |
mapFactory |
|||||||
Collector<T, A, D> |
downstream |
|||||||
partitioningBy |
Collector<T, ?, Map<Boolean, List<T>>> |
(T) -> boolean |
predicate |
条件を満たすグループと満たさないグループに分ける。 →Collectors.groupingBy() |
Stream<Character> s = Stream.of('a', 'B', 'c', 'd', 'E'); |
{false=[a, c, d], true=[B, E]} |
val s = Stream('a', 'B', 'c', 'd', 'E') |
|
partitioningBy |
Collector<T, ?, Map<Boolean, D>> |
(T) -> boolean |
predicate |
条件を満たすグループと満たさないグループに分ける。 各グループを集約する関数を渡す。 →Collectors.groupingBy() |
Stream<Character> s = Stream.of('a', 'B', 'c', 'd', 'E'); |
{false=acd, true=BE} |
val s = Stream('a', 'B', 'c', 'd', 'E') |
|
Collector<T, A, D> |
downstream |
|||||||
toMap |
Collector<T, ?, Map<K,U>> |
(T) -> K |
keyMapper |
Mapに変換する。 キーと値を抽出する関数を渡す。 キーが重複すると例外が発生する。(Scalaだと後の値が生き残る) |
Stream<String> s = Stream.of("a-foo",
"b-bar", "c-zzz"); |
{a=foo, b=bar, c=zzz} |
val s = Stream("a-foo", "b-bar", "c-zzz") |
|
(T) -> U |
valueMapper |
|||||||
toMap |
Collector<T, ?, Map<K,U>> |
(T) -> K |
keyMapper |
Mapに変換する。 キーが既に存在している場合はmergeFunctionが呼ばれて値が結合される。 |
Stream<String> s = Stream.of("a-foo",
"b-bar", "c-zzz", "b-hoge", "a-hage"); |
{a=foo:hage, b=bar:hoge, c=zzz} |
||
(T) -> U |
valueMapper |
|||||||
(U, U) -> U |
mergeFunction |
|||||||
toMap |
Collector<T, ?, M> |
(T) -> K |
keyMapper |
Mapに変換する。 Mapのインスタンスを生成する関数を渡す。 |
Stream<String> s = Stream.of("a-foo",
"b-bar", "c-zzz", "b-hoge", "a-hage"); |
{a=foo:hage, b=bar:hoge, c=zzz} |
||
(T) -> U |
valueMapper |
|||||||
(U, U) -> U |
mergeFunction |
|||||||
() -> M |
mapSupplier |
|||||||
toUnmodifiableMap |
10 | Collector<T, ?, Map<K,U>> |
(T) -> K |
keyMapper |
不変Mapに変換する。[2018-06-02] | |||
(T) -> U |
valueMapper |
|||||||
toUnmodifiableMap |
10 | Collector<T, ?, Map<K,U>> |
(T) -> K |
keyMapper |
||||
(T) -> U |
valueMapper |
|||||||
(U, U) -> U |
mergeFunction |
|||||||
toConcurrentMap |
Collector<T, ?, ConcurrentMap<K,U>> |
(T) -> K |
keyMapper |
ConcurrentMapを利用したtoMap。 たぶん並列ストリームにおいてマルチスレッドで生成されるのだろう。 |
||||
(T) -> U |
valueMapper |
|||||||
toConcurrentMap |
Collector<T, ?, ConcurrentMap<K,U>> |
(T) -> K |
keyMapper |
|||||
(T) -> U |
valueMapper |
|||||||
(U, U) -> U |
mergeFunction |
|||||||
toConcurrentMap |
Collector<T, ?, M> |
(T) -> K |
keyMapper |
|||||
(T) -> U |
valueMapper |
|||||||
(U, U) -> U |
mergeFunction |
|||||||
() -> M |
mapSupplier |
|||||||
summarizingInt |
Collector<T, ?, IntSummaryStatistics> |
(T) -> int |
mapper |
各値をintに変換して各種集計値を算出する。 →IntStream#summaryStatistics() |
Stream<String> s = Stream.of("abc", "de", "f"); |
IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000,
max=3} |
||
summarizingLong |
Collector<T, ?, LongSummaryStatistics> |
(T) -> long |
mapper |
各値をlongに変換して各種集計値を算出する。 →LongStream#summaryStatistics() |
Stream<String> s = Stream.of("100", "20", "3"); |
LongSummaryStatistics{count=3, sum=123, min=3, average=41.000000,
max=100} |
||
summarizingDouble |
Collector<T, ?, DoubleSummaryStatistics> |
(T) -> double |
mapper |
各値をdoubleに変換して各種集計値を算出する。 →DoubleStream#summaryStatistics() |
Stream<String> s = Stream.of("1e2", "2e1", "3"); |
DoubleSummaryStatistics{count=3, sum=123.000000, min=3.000000,
average=41.000000, max=100.000000} |
||
teeing |
12 | R |
Collector<T, ?, R1> |
downstream1 |
2つのCollectorを適用する。[2019-03-31] | String s = Stream.of("a", "z", "c").collect(Collectors.teeing( |
"a-z" |
|
Collector<T, ?, R2> |
downstream2 |
|||||||
(R1, R2) -> R |
merger |
Collectorオブジェクトを自分で作ることも出来る。[2014-04-13]
Collectorオブジェクトは、Collector.ofメソッドを使って作るのが分かり易い。
Collector.of()には、Stream#collect(supplier,
accumulator, combiner)に渡す関数と同様のものを渡す。
(ただし、combinerの型は、collect()はBiConsumerだがCollectorはBinaryOperatorであり、異なっている)
→collectメソッドに渡す関数を作る例
Listを生成するCollectorはCollectors.toList()で用意されているが、例として実装してみる。[2014-04-13]
(→collectメソッドで自作List変換関数を渡す例)
import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collector.Characteristics;
public static final Collector<String, ?, List<String>> TO_LIST; static { Supplier<List<String>> supplier = () -> new ArrayList<>(10); BiConsumer<List<String>, String> accumulator = (l, t) -> l.add(t); BinaryOperator<List<String>> combiner = (l1, l2) -> { l1.addAll(l2); return l1; }; TO_LIST = Collector.of(supplier, accumulator, combiner, Characteristics.IDENTITY_FINISH); }
Stream<String> stream = Stream.of("a", "b", "c"); List<String> list = stream.collect(TO_LIST); System.out.println(list);
Collector.of()には結果を生成するのに使う関数を渡す。
Stream内の値(要素)の型をT(今回の例ではString)、欲しい型をR(今回の例ではList<String>)とすると、各関数の型は以下のようになる。
引数名 | 型 | 説明 | 例 | ||
---|---|---|---|---|---|
supplier | Supplier<R> | () -> R |
出力するクラス(R)のインスタンスを生成する関数。 | () -> new ArrayList<String>(10) |
|
accumulator | BiConsumer<R, T> | (R, T) -> void |
要素の値(T)を出力する値(R)に集約する関数。 | (list, t) -> list.add(t) |
|
combiner | BinaryOperator<R, R> | (R, R) -> R |
2つのRを1つにまとめる関数。 | (list1, list2)
-> { |
|
characteristics | Characteristics... | CONCURRENT、UNORDERED、IDENTITY_FINISHの組み合わせ。 | |||
CONCURRENT | マルチスレッドで呼ばれても大丈夫な場合に指定する。 | ||||
UNORDERED | 順序を保証しない場合に指定する。 | ||||
IDENTITY_FINISH | finisherが省略可能な場合に指定する。 (ofメソッドのオーバーロードに、finisherを指定するものがある) |
IntStreamの各値を文字コードと見なし、そこからStringを生成する例。[2014-04-13]
(→collectメソッドでString変換関数を渡す例)
import java.util.stream.IntStream;
public static final Collector<Integer, ?, String> TO_STRING; static { Supplier<StringBuilder> supplier = StringBuilder::
new; BiConsumer<StringBuilder, Integer> accumulator = (sb, c) -> sb.append((char) c.intValue()); BinaryOperator<StringBuilder> combiner = (sb1, sb2) -> sb1.append(sb2); Function<StringBuilder, String> finisher = sb -> sb.toString(); // Supplier<StringBuilder> supplier = StringBuilder::
new; // BiConsumer<StringBuilder, Integer> accumulator = (sb, c) -> sb.append((char) (int) c); // BinaryOperator<StringBuilder> combiner = StringBuilder::
append; // Function<StringBuilder, String> finisher = StringBuilder::
toString; TO_STRING = Collector.of(supplier, accumulator, combiner, finisher); }
IntStream stream = IntStream.of(0x30, 0x31, 0x41, 0x42, 0x43); // IntStream stream = "01ABC".chars(); String s = stream.boxed().collect(TO_STRING); System.out.println(s);
この例では、引数にfinisherがあるCollector.ofメソッドを呼び出している。
Stream内の値(要素)の型をT(今回の例ではInteger)、集約用の型をA(今回の例ではStringBuilder)、欲しい型をR(今回の例ではString)とすると、各関数の型は以下のようになる。
引数名 | 型 | 説明 | 例 | ||
---|---|---|---|---|---|
supplier | Supplier<A> | () -> A |
集約用クラス(A)のインスタンスを生成する関数。 | () -> new
StringBuilder(16) |
|
accumulator | BiConsumer<A, T> | (A, T) -> void |
要素の値(T)をAに集約する関数。 | (sb, t) ->
sb.append(t) |
|
combiner | BinaryOperator<A, A> | (A, A) -> A |
2つのAを1つにまとめる関数。 | (sb1, sb2) -> { |
|
finisher | Function<A, R> | (A) -> R |
集約用クラス(A)から最終的に欲しい型(R)に変換する関数。 | (sb) -> sb.toString() |
|
characteristics | Characteristics... | CONCURRENT、UNORDERED、IDENTITY_FINISHの組み合わせ。 | |||
CONCURRENT | マルチスレッドで呼ばれても大丈夫な場合に指定する。 | ||||
UNORDERED | 順序を保証しない場合に指定する。 | ||||
IDENTITY_FINISH | finisherが省略可能な場合に指定する。 |
ちなみにコードポイントの場合は以下の様になる。(呼び出すappendメソッドが違うだけで、大して変わらない^^;)
public static final Collector<Integer, ?, String> TO_STRING; static { TO_STRING = Collector.of( StringBuilder::
new, StringBuilder::
appendCodePoint, StringBuilder::
append, StringBuilder::
toString ); }
IntStream stream = IntStream.of(0x30, 0x31, 0x41, 0x42, 0x43); // IntStream stream = "01ABC".codePoints(); String s = stream.boxed().collect(TO_STRING); System.out.println(s);
なお、TO_STRINGの型は厳密には「Collector<Integer, StringBuilder, String>
」だが、
集約用のクラスがStringBuilderであることはTO_STRINGを使う側には関係がないので(使う側からすれば、Streamの要素の型がIntegerで出力がStringであることが重要(そこが一致していないとコンパイルエラーになるから))、
「?」にしてしまっている。
あと、IntStreamには「Integerを受け取るCollector」は渡せないので(そもそもIntStreamにはCollectorを受け取るcollectメソッドは無いのだがorz)、
boxed()を使ってStream<Integer>に変換している。