Collectors.converting

Louis Wasserman lowasser at google.com
Tue Jan 29 20:55:17 UTC 2019


I'm not sure I follow how this is intended to be different from
Collectors.collectingAndThen.  Could you explain that a little more?

On Tue, Jan 29, 2019 at 12:31 PM Peter Levart <peter.levart at gmail.com>
wrote:

> 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
>
>

-- 
Louis Wasserman


More information about the core-libs-dev mailing list