--- old/src/java.base/share/classes/java/util/stream/Collectors.java 2018-08-07 12:48:36.339012000 +0700 +++ new/src/java.base/share/classes/java/util/stream/Collectors.java 2018-08-07 12:48:36.031012000 +0700 @@ -1885,6 +1885,78 @@ } /** + * Returns a {@code Collector} that passes the input elements to two specified collectors + * and merges their results with the specified merge function. + * + *

The resulting collector is {@link Collector.Characteristics#UNORDERED} if both supplied + * collectors are unordered and {@link Collector.Characteristics#CONCURRENT} if both supplied + * collectors are concurrent. + * + * @param the type of the input elements + * @param the result type of the first collector + * @param the result type of the second collector + * @param the final result type + * @param c1 the first collector + * @param c2 the second collector + * @param finisher the function which merges two results into the single one + * @return a {@code Collector} which aggregates the results of two supplied collectors. + * @since 12 + */ + public static + Collector pairing(Collector c1, + Collector c2, + BiFunction finisher) { + return pairing0(c1, c2, finisher); + } + + private static + Collector pairing0(Collector c1, + Collector c2, + BiFunction finisher) { + Objects.requireNonNull(c1, "c1"); + Objects.requireNonNull(c2, "c2"); + Objects.requireNonNull(finisher, "finisher"); + EnumSet c = EnumSet.noneOf(Collector.Characteristics.class); + c.addAll(c1.characteristics()); + c.retainAll(c2.characteristics()); + c.remove(Collector.Characteristics.IDENTITY_FINISH); + + Supplier c1Supplier = Objects.requireNonNull(c1.supplier(), "c1 supplier"); + Supplier c2Supplier = Objects.requireNonNull(c2.supplier(), "c2 supplier"); + BiConsumer c1Accumulator = Objects.requireNonNull(c1.accumulator(), "c1 accumulator"); + BiConsumer c2Accumulator = Objects.requireNonNull(c2.accumulator(), "c2 accumulator"); + BinaryOperator c1Combiner = Objects.requireNonNull(c1.combiner(), "c1 combiner"); + BinaryOperator c2Combiner = Objects.requireNonNull(c2.combiner(), "c2 combiner"); + Function c1Finisher = Objects.requireNonNull(c1.finisher(), "c1 finisher"); + Function c2Finisher = Objects.requireNonNull(c2.finisher(), "c2 finisher"); + + class PairBox { + A1 left = c1Supplier.get(); + A2 right = c2Supplier.get(); + + void add(T t) { + c1Accumulator.accept(left, t); + c2Accumulator.accept(right, t); + } + + PairBox combine(PairBox other) { + left = c1Combiner.apply(left, other.left); + right = c2Combiner.apply(right, other.right); + return this; + } + + R get() { + R1 r1 = c1Finisher.apply(left); + R2 r2 = c2Finisher.apply(right); + return finisher.apply(r1, r2); + } + } + + return new CollectorImpl<>(PairBox::new, PairBox::add, PairBox::combine, PairBox::get, + Collections.unmodifiableSet(c)); + } + + /** * Implementation class used by partitioningBy. */ private static final class Partition