JavaSE8 Goldへの道(Upgrade to Java SE 8 Programmer 1Z0-810 試験対策)10回目です。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
一連の記事は「JavaSE8Gold」ラベルを付けていきます。
Stream#collectメソッドに渡すCollectorの基本的な実装を集めたCollectorsクラスについて、その6でも少しご紹介しましたが、まだ試験範囲から紹介できていないものがあるので今回はそれをご紹介します。Collectorsクラスの各種メソッド
toXxx()
最も基本なのは
toList()とtoSet()メソッドでしょう。ストリームの要素をList、Setに集約します。
当然ながら、
toSet()の方は要素の重複があると1つになってしまいます。//staticインポート staticメソッドをクラス名指定なしで記述できる
//以降の例は全て定義済みとする
import static java.util.stream.Collectors.*;
:
:
Set<String> set = Stream.of("a", "b", "a", "c", "d")
.collect(toSet());
System.out.println(set);
実行結果
[a, b, c, d]
実装を見ると、返されるのはそれぞれ
ArrayList、HashSetのようです。他のコレクションにしたい場合は
toCollection(Supplier<C>)メソッドを使います。例えばコンテナとして
TreeSetを使いたい場合は以下のようにします。Set<String> set = Stream.of("a", "b", "a", "c", "d")
.collect(toCollection(TreeSet::new));
Supplierには使いたいクラスのインスタンスを生成してやればよく、コンストラクタ参照で記述できます。ラムダ式で書けば
() -> new TreeSet()です。少々ややこしいのが
toMap()です。引数の違いで3種類ありますが、一番引数が少ないものの定義は以下のようになっています。public static <T,K,U> Collector<T,?,Map<K,U>> toMap (Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper)
要するに、要素1つからキーと値を算出してあげなければなりません。
例えば社員クラスのストリームから社員IDをキーとしてマップに変換するには以下のようにします。
Map<String, Employee> map = Stream.of(new Employee("1", "アムロ"), new Employee("2", "カイ"), new Employee("3", "ハヤト"))
.collect(toMap(e -> e.getId() , e -> e));
//このようにも書ける
//.collect(toMap(Employee::getId , Function.identity()));
System.out.println(map);
実行結果(※
EmployeeにはtoStringが実装済み)
{1=アムロ, 2=カイ, 3=ハヤト}
コメント行のように書くこともできます。第一引数はメソッド参照、第二引数は要素それ自身を返せばいいのですが、
このメソッドではキーが重複する場合
これを回避したい場合は引数を3つ取る
引数4つのものは
e->eと書くのもいまいちなのでFunctionインタフェースに用意されているユーティリティメソッドFunction#identityが使えます。このメソッドではキーが重複する場合
IllegalStateExceptionがスローされます。これを回避したい場合は引数を3つ取る
toMap()が使えます。第三引数にはキーが重複した場合に2つの値を合成するためのBinaryOperatorを渡します。上記例では合成しようがありませんが、例えば値が文字列の場合結合して1つの文字列にするといったことができます。引数4つのものは
Mapの型を指定できます。public static <T,K,U> Collector<T,?,Map<K,U>> toMap (Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
public static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap (Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory)
groupingBy
SQLのgroup byのようにグループ化を行います。基本は
Map<K,List<T>>型として、キーごとに要素をリストに入れたものを生成します。Map<String, List<Employee>> map = Stream.of(new Employee("地球連邦", "アムロ"), new Employee("地球連邦", "カイ"), new Employee("地球連邦", "ハヤト"),
new Employee("ジオン公国", "シャア"), new Employee("ジオン公国", "ガトー"), new Employee("ジオン公国", "ラル"))
.collect(groupingBy(Employee::getId));
System.out.println(map);
実行結果
{地球連邦=[アムロ, カイ, ハヤト], ジオン公国=[シャア, ガトー, ラル]}
集約方法を
List以外にしたい場合、Mapを他の実装にしたい/別の集計にしたい場合は引数の異なるgroupingByが用意されているのでそちらが使えます。public static <T,K,A,D> Collector<T,?,Map<K,D>> groupingBy (Function<? super T,? extends K> classifier,
Collector<? super T,A,D> downstream)
public static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M> groupingBy (Function<? super T,? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T,A,D> downstream)
downstreamに別のCollector(例えばtoSet())を指定することで別の集約が可能です。Collectors#counting()を渡してグループの要素数をカウントするといったこともできます。pertitoningBy
groupingByのより単純なもので、
PredicateによってBoolean.TRUEとBoolean.FALSEをキーに分割します。Map<Boolean, List<Employee>> map = Stream.of(new Employee("地球連邦", "アムロ"), new Employee("地球連邦", "カイ"), new Employee("地球連邦", "ハヤト"),
new Employee("ジオン公国", "シャア"), new Employee("ジオン公国", "ガトー"), new Employee("ジオン公国", "ランバ・ラル"))
.collect(partitioningBy(e -> e.getId().equals("地球連邦")));
System.out.println(map);
実行結果
{false=[シャア, ガトー, ランバ・ラル], true=[アムロ, カイ, ハヤト]}
これも引数の異なるバージョンで
List以外の集約も可能です。public static <T,D,A> Collector<T,?,Map<Boolean,D>> partitioningBy (Predicate<? super T> predicate,
Collector<? super T,A,D> downstream)
averagingXxx
各プリミティブ型ごとにメソッドが用意されています。
要素に値変換関数(
要素に値変換関数(
ToDoubleFunction等)を通した結果の平均を求めます。double avg = Stream.of("A", "BB", "CCC", "DDDD", "EEEEE")
.collect(averagingInt(s -> s.length()));
System.out.println(avg);
実行結果
3.0
結果の型は全て
その7で説明したプリミティブストリームを使っても同じ結果が得られますが、その場合戻り値は
Doubleです。その7で説明したプリミティブストリームを使っても同じ結果が得られますが、その場合戻り値は
OptionalDoubleなのでもう一つ手順が必要になります。double avg = Stream.of("A", "BB", "CCC", "DDDD", "EEEEE")
.mapToInt(s -> s.length())
.average()
.getAsDouble(); //OptionalDoubleから値を取得
System.out.println(avg);
OptionalDoubleなのは要素が0だった場合に値なし(null)となるためですが、ではaveragingXxxの方でやってみると・・・double avg = Stream.<String>empty() //型推論ができないので指定する必要あり
.collect(averagingInt(s -> s.length()));
System.out.println(avg);
実行結果
0.0
となり0になりました。ちょっと統一取れてない感じです。
joining
要素が
CharSequenceの場合に、すべての要素を結合して一つの文字列にします。String str = Stream.of(new Employee("地球連邦", "アムロ"), new Employee("地球連邦", "カイ"), new Employee("地球連邦", "ハヤト"),
new Employee("ジオン公国", "シャア"), new Employee("ジオン公国", "ガトー"), new Employee("ジオン公国", "ラル"))
.map(e -> e.getName())
.collect(joining());
System.out.println(str);
実行結果
アムロカイハヤトシャアガトーラル
引数なしだとそのまま結合、引数1つだと区切り文字を指定できます。最後の要素の後はカンマなしに・・とか考えることなく、カンマ区切りテキストも簡単に生成できます。
3つの場合接頭辞と接尾辞が指定できます。引数の順番に注意です。
3つの場合接頭辞と接尾辞が指定できます。引数の順番に注意です。
String str = Stream.of(new Employee("地球連邦", "アムロ"), new Employee("地球連邦", "カイ"), new Employee("地球連邦", "ハヤト"),
new Employee("ジオン公国", "シャア"), new Employee("ジオン公国", "ガトー"), new Employee("ジオン公国", "ラル"))
.map(e -> e.getName())
.collect(joining(",", "「", "」"));
System.out.println(str);
実行結果
「アムロ,カイ,ハヤト,シャア,ガトー,ラル」
と、い、う、わ、け、で
Collectorsクラスにはもう少しメソッドがあるのですが、試験範囲じゃないっぽいのでこの辺にしておきます。
興味のある方はAPIドキュメントを御覧ください。
今回も以下のサイトとGoldの通常試験の参考書を参考にしています。

