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();
            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);
        }
    }
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 ( ) ; 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 ) ; } }
Returns a {@code Collector} implementing a cascaded "group by" operation
on input elements of type {@code T}, grouping elements according to a
classification function, and then performing a reduction operation on
the values associated with a given key using the specified downstream
{@code Collector}.  The {@code Map} produced by the Collector is created
with the supplied factory function.

<p>The classification function maps elements to some key type {@code K}.
The downstream collector operates on elements of type {@code T} and
produces a result of type {@code D}. The resulting collector produces a
{@code Map<K, D>}.

<p>For example, to compute the set of last names of people in each city,
where the city names are sorted:
<pre>{@code
Map<City, Set<String>> namesByCity
= people.stream().collect(groupingBy(Person::getCity, TreeMap::new,
mapping(Person::getLastName, toSet())));
}</pre>

@implNote
The returned {@code Collector} is not concurrent.  For parallel stream
pipelines, the {@code combiner} function operates by merging the keys
from one map into another, which can be an expensive operation.  If
preservation of the order in which elements are presented to the downstream
collector is not required, using {@link #groupingByConcurrent(Function, Supplier, Collector)}
may offer better parallel performance.

@param <T> the type of the input elements
@param <K> the type of the keys
@param <A> the intermediate accumulation type of the downstream collector
@param <D> the result type of the downstream reduction
@param <M> the type of the resulting {@code Map}
@param classifier a classifier function mapping input elements to keys
@param downstream a {@code Collector} implementing the downstream reduction
@param mapFactory a function which, when called, produces a new empty
{@code Map} of the desired type
@return a {@code Collector} implementing the cascaded group-by operation

@see #groupingBy(Function, Collector)
@see #groupingBy(Function)
@see #groupingByConcurrent(Function, Supplier, Collector)