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

Reply via email to