Collectors.converting

Brian Goetz brian.goetz at oracle.com
Tue Jan 29 20:52:25 UTC 2019


How is this different from Collectors.collectingAndThen?

On 1/29/2019 3:30 PM, Peter Levart 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
>



More information about the core-libs-dev mailing list