Understanding Stream Reduction Implementation

I am trying to understand the downstream reduction implementation in JDK. Here he is:

public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) { Supplier<A> downstreamSupplier = downstream.supplier(); BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator(); BiConsumer<Map<K, A>, T> accumulator = (m, t) -> { K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); downstreamAccumulator.accept(container, t); }; BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner()); @SuppressWarnings("unchecked") Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory; if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID); } else { @SuppressWarnings("unchecked") Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher(); //1, <------------- HERE Function<Map<K, A>, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") M castResult = (M) intermediate; return castResult; }; return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID); } } 

In //1 , downstreamFinisher is of type Function<A, D> . Judging by the declarations of parameters of type <T, K, D, A, M extends Map<K, D>> , type D is independent of A So why do we throw it on Function<A, A> . I think type D may not even be a subclass of A

What did I miss?

+4
java java-8 collectors
source share
1 answer

This collector actually violates the security of the type of the universal Map type if the downstream collector does not have an identification finisher, and the finisher function returns a different type than the type of the intermediate container.

During the collection operation, the map will contain objects of type A , that is, the type of intermediate container. Then, at the end of the operation, the groupingBy s finisher will go through the card and apply the finisher function to each value and replace it with the final result.

Of course, this feature cannot be realized without uncontrolled operations. There are several ways to do this, the option you posted changes the type of map provider from Supplier<M> to Supplier<Map<K, A>> (the first unchecked operation), so the compiler agrees that the map will contain values โ€‹โ€‹of type A not D This is why the finisher function finisher to be changed to Function<A,A> (second unchecked operation), so it can be used in a map operation with replaceAll , which seems to require objects of type A , despite its actual D Finally, the result map must be transferred to M (the third unchecked operation) in order to get the object of the expected result type M , which the supplier actually provided.

The correct alternative to the type would be to use different cards and perform the finishing operation by filling in the results map by converting the values โ€‹โ€‹of the intermediate map. This may not only be a costly operation, but an intermediate card will require a second provider, as the provided provider creates cards suitable for the final result. Apparently, the developers decided that this was an acceptable violation of type safety.

Note that you may notice an unsafe operation when you try to use a Map implementation that provides type safety:

 Stream.of("foo", "bar").collect(Collectors.groupingBy(String::length, () -> Collections.checkedMap(new HashMap<>(), Integer.class, Long.class), Collectors.counting())); 

will ClassCastException with this implementation because it is trying to put an intermediate container (array) instead of Long in the Map.

+5
source share

All Articles