Collectors.converting

Brian Goetz brian.goetz at oracle.com
Wed Jan 30 16:35:05 UTC 2019


Arguably, it could have been exposed as a default method on Collector 
(Collector::andThen) -- but we avoided this approach out of concern that 
a generic methods in receiver position can interact with type inference 
in ways that confuse people.

On 1/30/2019 3:22 AM, Peter Levart wrote:
>
>
> On 1/29/19 9:52 PM, Brian Goetz wrote:
>> How is this different from Collectors.collectingAndThen?
>
> Hi Brian,
>
> It is exactly the same!
>
> I'm sorry, I haven discovered that method when I needed it. Perhaps I 
> was looking for another name?
>
> Regards, Peter
>
>>
>> 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