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