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の通常試験の参考書を参考にしています。