Skip to content

Instantly share code, notes, and snippets.

@gakuzzzz
Created July 11, 2016 08:09
Show Gist options
  • Save gakuzzzz/a8b68317e3fcbb258291ef50c377efba to your computer and use it in GitHub Desktop.
Save gakuzzzz/a8b68317e3fcbb258291ef50c377efba to your computer and use it in GitHub Desktop.
Java8 Stream API の Collector を合成する

Java8 の Stream API は Stream#collect メソッドを使って様々な集約を行う事ができます。

java.util.stream.Collectors で様々な Collector が提供されていますが、Collectors.collectingAndThen のような合成が中心で同時に二つの集約を行うといった事が簡単にできません。

そのため、ラムダ禁止について本気出して考えてみた - 9つのパターンで見るStream API の「7. streamの外に結果を残す(禁止度:A)」で書かれている様な思わず禁止したくなっちゃう様なコードを書かざるを得ません。

// 引用: ラムダ禁止について本気出して考えてみた
//       7. streamの外に結果を残す(禁止度:A)
void averageAndSum1(List<Emp> list) {
    final Map<String, Integer> dummy = new HashMap<>();
    dummy.put("RESULT", 0);
    double ave = list.stream()
            .mapToInt(emp -> {
                int sal = emp.getSal();
                dummy.put("RESULT", dummy.get("RESULT") + sal);
                return sal;
            })
            .average()
            .getAsDouble();
    System.out.println("ave=" + ave + ",sum=" + dummy.get("RESULT"));
}

そこで、複数の Collector を合成して一つの Collector にする product Collector を作成しました。

これを使うと以下の様に、複数の集約したい場合に上記のようなややこしい事をせず、直感的に記述する事が可能になります。 (上記の例はSummaryStatistics使うと簡単に書き変えられるのでちょっとお題を変えてます)

public void richmanByDeptAndTotalSummaryOfSalary(final List<Emp> list) {
  final T2<Map<Department, Optional<Emp>>, IntSummaryStatistics> result = list.stream()
    .collect(product(
      groupingBy(Emp::getDepartment, maxBy(comparingInt(Emp::getSalary))), // 部署毎で一番給与の多い従業員
      summarizingInt(Emp::getSalary)                                       // 全体の給与のサマリ(平均とか最大値・最小値など)
    ));

  System.out.println("mostRichmanByDept=" + result._1 + ", salarySummary=" + result._2);
}

これならラムダ禁止とか言われなくて済みますね!

MIT License にしとくので適当にコピペして使ってください。

/*
* MapStreamSyntax
*
* Copyright(c) gakuzzzz
*
* This software is released under the MIT License.
* http://opensource.org/licenses/mit-license.php
*/
package jp.t2v.lab.ds;
import jp.t2v.lab.ds.Tuples.T2;
import java.util.EnumSet;
import java.util.function.BiFunction;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.Set;
import static java.util.stream.Collector.Characteristics.CONCURRENT;
import static java.util.stream.Collector.Characteristics.UNORDERED;
public class Collectors2 {
private Collectors2() {}
public static <T, A1, A2, R1, R2> Collector<T, ?, T2<R1, R2>> product(Collector<? super T, A1, ? extends R1> a, Collector<? super T, A2, ? extends R2> b) {
return productWith(a, b, T2::of);
}
public static <T, A1, A2, R1, R2, C> Collector<T, ?, C> productWith(Collector<? super T, A1, ? extends R1> a, Collector<? super T, A2, ? extends R2> b, BiFunction<? super R1, ? super R2, ? extends C> f) {
final Set<Characteristics> characteristics = EnumSet.of(CONCURRENT, UNORDERED);
characteristics.retainAll(a.characteristics());
characteristics.retainAll(b.characteristics());
return Collector.of(
() -> T2.of(a.supplier().get(), b.supplier().get()),
(acc, t) -> {
a.accumulator().accept(acc._1, t);
b.accumulator().accept(acc._2, t);
},
(a1, a2) -> T2.of(
a.combiner().apply(a1._1, a2._1),
b.combiner().apply(a1._2, a2._2)
),
(acc) -> f.apply(
a.finisher().apply(acc._1),
b.finisher().apply(acc._2)
),
characteristics.stream().toArray(Characteristics[]::new)
);
}
}
/*
* MapStreamSyntax
*
* Copyright(c) gakuzzzz
*
* This software is released under the MIT License.
* http://opensource.org/licenses/mit-license.php
*/
package jp.t2v.lab.ds;
import java.util.Objects;
public class Tuples {
private Tuples() {}
public static final class T2<A, B> {
public final A _1;
public final B _2;
private T2(final A _1, final B _2) {
Objects.requireNonNull(_1);
Objects.requireNonNull(_2);
this._1 = _1;
this._2 = _2;
}
public static <X, Y> T2<X, Y> of(final X x, final Y y) {
return new T2<>(x, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment