S-JIS[2014-04-12/2017-09-24] 変更履歴

Java Collector

JavaStreamcollectメソッドで使われる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

Collectorsクラスには、Stream#collect(Collector)メソッドに渡すCollectorインターフェースの具象クラス(を返すメソッド)が色々用意されている。

import java.util.stream.Collectors;
メソッド 戻り型 引数 説明 Scala相当
コーディング 結果
toCollection Collector<T, ?, C> () -> C collectionFactory コレクションに変換する。
コレクションインスタンスを生成する関数を渡す。
Stream<String> s = Stream.of("a", "b", "c");
Queue<String> queue = s.collect(Collectors.toCollection(LinkedBlockingQueue::new));
System.out.println(queue);
[a, b, c]  
toList Collector<T, ?, List<T>>     Listに変換する。 Stream<String> s = Stream.of("a", "b", "c");
List<String> list = s.collect(Collectors.toList());
System.out.println(list);
[a, b, c] val s = Stream("a", "b", "c")
val list = s.toList
//  list = s.to[List]
println(list)
toSet Collector<T, ?, Set<T>>     Setに変換する。 Stream<String> s = Stream.of("a", "b", "c");
Set<String> set = s.collect(Collectors.toSet());
System.out.println(set);
[a, b, c] val s = Stream("a", "b", "c")
val set = s.toSet
//  set = s.to[Set]
println(set)
joining Collector<CharSequence, ?, String>     結合してStringにする。
StringJoiner
Stream<String> s = Stream.of("a", "b", "c");
String str = s.collect(Collectors.joining());
System.out.println(str);
abc val s = Stream("a", "b", "c")
val str = s.mkString
println(str)
joining Collector<CharSequence, ?, String> CharSequence delimiter 結合してStringにする。
区切り文字を指定する。
StringJoiner
Stream<String> s = Stream.of("a", "b", "c");
String str = s.collect(Collectors.joining(":"));
System.out.println(str);
a:b:c val s = Stream("a", "b", "c")
val str = s.mkString(":")
println(str)
joining Collector<CharSequence, ?, String> CharSequence delimiter 結合してStringにする。
区切り文字・前後文字を指定する。
StringJoiner
Stream<String> s = Stream.of("a", "b", "c");
String str = s.collect(Collectors.joining(":", "<", ">"));
System.out.println(str);
<a:b:c> val s = Stream("a", "b", "c")
val str = s.mkString("<", ":", ">")
println(str)
CharSequence prefix
CharSequence suffix
mapping Collector<T, ?, R> (T) -> U mapper 値を変換して他のコレクターを呼び出す。 Stream<Character> s = Stream.of('a', 'b', 'c');
String str = s.collect(Collectors.mapping(c -> c.toString(), Collectors.joining(":")));
System.out.println(str);
a:b:c  
Collector<U, A, R> downstream
flatMapping Collector<T, ?, R> (T) -> Stream<U> mapper Java9以降。[2017-09-24]
値をStreamに変換して他のコレクターを呼び出す。
Stream<String> s = Stream.of("abc", "bcd", "zz");
Map<Integer, Set<String>> map = s.collect(Collectors.groupingBy(t -> t.length(), Collectors.flatMapping(t -> t.chars().mapToObj(c -> Character.toString((char) c)), Collectors.toSet())));
{2=[z], 3=[a, b, c, d]}  
Collector<U, A, R> downstream
filtering Collector<T, ?, R> (T) -> boolean predicate Java9以降。[2017-09-24]
条件を満たす値だけ他のコレクターに渡す。
Stream<String> s = Stream.of("abc", "bcd", "zz", "yy");
Map<Integer, List<String>> map = s.collect(Collectors.groupingBy(t -> t.length(), Collectors.filtering(t -> t.charAt(0) != 'z', Collectors.toList())));
{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");
List<String> list = s.collect(Collectors.collectingAndThen(Collectors.toList(),
l -> Collections.unmodifiableList(l)));
System.out.println(list);
[a, b, c]  
(R) -> RR finisher
counting Collector<T, ?, Long>     要素数を取得する。
Stream#count()
Stream<String> s = Stream.of("a", "b", "c");
long n = s.collect(Collectors.counting());
System.out.println(n);
3 val s = Stream("a", "b", "c")
val n = s.size
println(n)
minBy Collector<T, ?, Optional<T>> Comparator<T> comparator 最小値を取得する。
Stream#min()
Stream<String> s = Stream.of("a", "b", "c");
Optional<String> m = s.collect(Collectors.minBy(Comparator.naturalOrder()));
System.out.println(m);
Optional[a] val s = Stream("a", "b", "c")
val m = if (s.isEmpty) None else Some(s.min)
println(m)
maxBy Collector<T, ?, Optional<T>> Comparator<T> comparator 最大値を取得する。
Stream#max()
Stream<String> s = Stream.of("a", "b", "c");
Optional<String> m = s.collect(Collectors.maxBy(Comparator.naturalOrder()));
System.out.println(m);
Optional[c] val s = Stream("a", "b", "c")
val m = if (s.isEmpty) None else Some(s.max)
println(m)
summingInt Collector<T, ?, Integer> (T) -> int mapper 各値をintに変換し、合算する。
Stream#mapToInt()IntStream#sum()
Collectors.summarizingInt()
Stream<String> s = Stream.of("abc", "de", "f");
int len = s.collect(Collectors.summingInt(t -> t.length()));
System.out.println(len);
6 val s = Stream("abc", "de", "f")
val len = s.map(_.length).sum
println(len)
summingLong Collector<T, ?, Long> (T) -> long mapper 各値をlongに変換し、合算する。
Stream#mapToLong()LongStream#sum()
Collectors.summarizingLong()
Stream<String> s = Stream.of("100", "20", "3");
long n = s.collect(Collectors.summingLong(t -> Long.parseLong(t)));
System.out.println(n);
123 val s = Stream("100", "20", "3")
val n = s.map(_.toLong).sum
println(n)
summingDouble Collector<T, ?, Double> (T) -> double mapper 各値をdoubleに変換し、合算する。
Stream#mapToDouble()DoubleStream#sum()
Collectors.summarizingDouble()
Stream<String> s = Stream.of("1e2", "2e1", "3");
double n = s.collect(Collectors.summingDouble(t -> Double.parseDouble(t)));
System.out.println(n);
123.0 val s = Stream("1e2", "2e1", "3")
val n = s.map(_.toDouble).sum
println(n)
averagingInt Collector<T, ?, Double> (T) -> int mapper 各値をintに変換し、平均値を算出する。
空ストリームの場合は0を返す。
Stream#mapToInt()IntStream#average()
Collectors.summarizingInt()
Stream<String> s = Stream.of("abc", "de", "f");
double ave = s.collect(Collectors.averagingInt(String::length));
System.out.println(ave);
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");
double ave = s.collect(Collectors.averagingLong(Long::parseLong));
System.out.println(ave);
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");
double ave = s.collect(Collectors.averagingDouble(Double::parseDouble));
System.out.println(ave);
41.0  
reducing Collector<T, ?, T> T identity 値を集約する。
Stream#reduce()
Stream<String> s = Stream.of("a", "b", "c");
String r = s.collect(Collectors.reducing("1", (i, t) -> i + t));
System.out.println(r);
1abc val s = Stream("a", "b", "c")
val r = s.fold("1")(_ + _)
println(r)
(T, T) -> T op
reducing Collector<T, ?, Optional<T>> (T, T) -> T op 値集約する。
Stream#reduce()
Stream<String> s = Stream.of("a", "b", "c");
Optional<String> r = s.collect(Collectors.reducing((i, t) -> i + t));
System.out.println(r);
Optional[abc] val s = Stream("a", "b", "c")
val r = s.reduceOption(_ + _)
println(r)
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");
Map<Integer, List<String>> m = s.collect(Collectors.groupingBy(t -> t.length()));
System.out.println(m);
{1=[a, c], 3=[bar, foo, zzz]} val s = Stream("a", "bar", "c", "foo", "zzz")
val m = s.groupBy(_.length).mapValues(_.toList)
println(m)
groupingBy Collector<T, ?, Map<K, D>> (T) -> K classifier 値をグループ分けする。
グループ分けされた値群を集約する関数を渡す。
Stream<String> s = Stream.of("a", "bar", "c", "foo");
Map<Integer, String> m = s.collect(Collectors.groupingBy(t -> t.length(), Collectors.joining()));
System.out.println(m);
{1=ac, 3=barfoo} val s = Stream("a", "bar", "c", "foo")
val m = s.groupBy(_.length).mapValues(_.mkString)
println(m)
Collector<T, A, D> downstream
groupingBy Collector<T, ?, M> (T) -> K classifier 値をグループ分けする。
Mapのインスタンスを生成する関数を渡す。
Stream<String> s = Stream.of("a", "bar", "c", "foo");
Map<Integer, String> m = s.collect(Collectors.groupingBy(t -> t.length(), TreeMap::new, Collectors.joining()));
System.out.println(m);
{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');
Map<Boolean, List<Character>> m = s.collect(Collectors.partitioningBy(c -> Character.isUpperCase(c)));
System.out.println(m);
{false=[a, c, d], true=[B, E]} val s = Stream('a', 'B', 'c', 'd', 'E')
val (t, f) = s.partition(_.isUpper)
println(f.toList)
println(t.toList)
partitioningBy Collector<T, ?, Map<Boolean, D>> (T) -> boolean predicate 条件を満たすグループと満たさないグループに分ける。
各グループを集約する関数を渡す。
Collectors.groupingBy()
Stream<Character> s = Stream.of('a', 'B', 'c', 'd', 'E');
Map<Boolean, String> m = s.collect(Collectors.partitioningBy(
  c -> Character.isUpperCase(c),
  Collectors.mapping(c -> c.toString(), Collectors.joining()))
);
System.out.println(m);
{false=acd, true=BE} val s = Stream('a', 'B', 'c', 'd', 'E')
val m = s.groupBy(_.isUpper).mapValues(_.mkString)
println(m)
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");
Map<String, String> m = s.collect(Collectors.toMap(t -> t.split("-")[0], t -> t.split("-")[1]));
System.out.println(m);
{a=foo, b=bar, c=zzz} val s = Stream("a-foo", "b-bar", "c-zzz")
val m = s.map{ t => val ss = t.split("-"); (ss(0), ss(1)) }.toMap
println(m)
(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");
Map<String, String> m = s.collect(Collectors.toMap(
  t -> t.split("-")[0],
  t -> t.split("-")[1],
  (v1, v2) -> v1 + ":" + v2
));
System.out.println(m);
{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");
Map<String, String> m = s.collect(Collectors.toMap(
  t -> t.split("-")[0],
  t -> t.split("-")[1],
  (v1, v2) -> v1 + ":" + v2,
  TreeMap::new
));
System.out.println(m);
{a=foo:hage, b=bar:hoge, c=zzz}  
(T) -> U valueMapper
(U, U) -> U mergeFunction
() -> M mapSupplier
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 d = s.collect(Collectors.summarizingInt(t -> t.length()));
System.out.println(d);
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 d = s.collect(Collectors.summarizingLong(t -> Long.parseLong(t)));
System.out.println(d);
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 d = s.collect(Collectors.summarizingDouble(t -> Double.parseDouble(t)));
System.out.println(d);
DoubleSummaryStatistics{count=3, sum=123.000000, min=3.000000, average=41.000000, max=100.000000}  

Collectorを作る例

Collectorオブジェクトを自分で作ることも出来る。[2014-04-13]

Collectorオブジェクトは、Collector.ofメソッドを使って作るのが分かり易い。
Collector.of()には、Stream#collect(supplier, accumulator, combiner)に渡す関数と同様のものを渡す。
(ただし、combinerの型は、collect()はBiConsumerだがCollectorはBinaryOperatorであり、異なっている)
collectメソッドに渡す関数を作る例


Listを生成するCollectorの例

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) -> {
  list1.addAll(list2);
  return list1;
}
characteristics Characteristics...   CONCURRENT、UNORDERED、IDENTITY_FINISHの組み合わせ。  
CONCURRENT マルチスレッドで呼ばれても大丈夫な場合に指定する。
UNORDERED 順序を保証しない場合に指定する。
IDENTITY_FINISH finisherが省略可能な場合に指定する。
(ofメソッドのオーバーロードに、finisherを指定するものがある)

Stringを生成するCollectorの例

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) -> {
  sb1.append(sb2);
  return sb1;
}
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>に変換している。


Streamへ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま