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