Collectors.converting

Peter Levart peter.levart at gmail.com
Tue Jan 29 20:30:37 UTC 2019


Hi,

I wonder if there's any interest in adding a convenience factory method 
for a Collector to the standard repertoire which would look like the 
following:

     /**
      * Convert given {@link Collector} so that it applies an additional 
finisher function that
      * converts the result of given collector. The characteristics of 
returned collector is
      * the same as that of the given inner collector but without any
      * {@link java.util.stream.Collector.Characteristics#IDENTITY_FINISH}.
      *
      * @param collector       the inner collector to delegate collection to
      * @param resultConverter the additional result finisher function
      * @param <T>             the type of input stream elements
      * @param <A>             the type of intermediate aggregation
      * @param <R>             the type of result of inner collector
      * @param <RR>            the type of final result
      * @return the converted collector
      */
     public static <T, A, R, RR> Collector<T, A, RR> converting(
         Collector<T, A, R> collector,
         Function<? super R, RR> resultConverter
     ) {
         return new Collector<T, A, RR>() {
             @Override
             public Supplier<A> supplier() { return collector.supplier(); }

             @Override
             public BiConsumer<A, T> accumulator() { return 
collector.accumulator(); }

             @Override
             public BinaryOperator<A> combiner() { return 
collector.combiner(); }

             @Override
             public Function<A, RR> finisher() { return 
resultConverter.compose(collector.finisher()); }

             @Override
             public Set<Characteristics> characteristics() {
                 EnumSet<Characteristics> characteristics = 
EnumSet.noneOf(Characteristics.class);
                 characteristics.addAll(collector.characteristics());
characteristics.remove(Characteristics.IDENTITY_FINISH);
                 return Collections.unmodifiableSet(characteristics);
             }
         };
     }


The rationale is as follows... Composability of collectors allows doing 
things like:

         interface Measurement {
             long value();
             String type();
         }

         Stream<Measurement> measurements = ....

         Map<String, LongSummaryStatistics> statsByType =
             measurements
                 .collect(
                     groupingBy(
                         Measurement::type,
                         summarizingLong(Measurement::value)
                     )
                 );

...but say I wanted the final result to be a map of avarage values by 
type and the values to be BigDecimal objects calculated with scale of 
19. I can create an intermediate map like above and then transform it to 
new map, like this:

         static BigDecimal toBDAverage(LongSummaryStatistics stats) {
             return BigDecimal.valueOf(stats.getSum())
                              .divide(
BigDecimal.valueOf(stats.getCount()),
                                  20, RoundingMode.HALF_EVEN);
         }

         Map<String, BigDecimal> averageByType =
             statsByType
                 .entrySet()
                 .stream()
                 .collect(toMap(Map.Entry::getKey, e -> 
toBDAverage(e.getValue())));

...this is ugly as it requires intermediate result to be materialized as 
a HashMap. Wouldn't it be possible to collect the original stream to 
final result in one go?

With the above Collectors.converting factory method, it would:

         Map<String, BigDecimal> averageByType =
             measurements
                 .collect(
                     groupingBy(
                         Measurement::type,
                         converting(
                             summarizingLong(Measurement::value),
                             stats -> toBDAverage(stats)
                         )
                     )
                 );

We already have Collectors.mapping(Function, Collector) method that 
constructs Collector that maps elements to be collected by inner 
collector. Collectors.converting(Collectors, Function) would be a twin 
brother that constructs Collector that converts the collection result of 
the inner collector. Both are useful in compositions like above, but we 
only have the 1st...

What do you think?

Regards, Peter



More information about the core-libs-dev mailing list