/** * Perform a partial mutable reduction using the supplied {@link Collector} * on a series of adjacent elements. * * <p> * This is a <a href="package-summary.html#StreamOps">quasi-intermediate</a> * partial reduction operation. * * @param <R> the type of the elements in the resulting stream * @param <A> the intermediate accumulation type of the {@code Collector} * @param collapsible a non-interfering, stateless predicate to apply to the * pair of adjacent elements of the input stream which returns true * for elements which should be collected together. * @param collector a {@code Collector} which is used to combine the * adjacent elements. * @return the new stream * @since 0.3.6 */ public <R, A> StreamEx<R> collapse(BiPredicate<? super T, ? super T> collapsible, Collector<? super T, A, R> collector) { Supplier<A> supplier = collector.supplier(); BiConsumer<A, ? super T> accumulator = collector.accumulator(); StreamEx<A> stream = collapseInternal(collapsible, t -> { A acc = supplier.get(); accumulator.accept(acc, t); return acc; }, (acc, t) -> { accumulator.accept(acc, t); return acc; }, collector.combiner()); if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { @SuppressWarnings("unchecked") StreamEx<R> result = (StreamEx<R>) stream; return result; } return stream.map(collector.finisher()); }
@SuppressWarnings("unchecked") static <K, D, A, M extends Map<K, D>> PartialCollector<Map<K, A>, M> grouping(Supplier<M> mapFactory, Collector<?, A, D> downstream) { BinaryOperator<A> downstreamMerger = downstream.combiner(); BiConsumer<Map<K, A>, Map<K, A>> merger = (map1, map2) -> { for (Map.Entry<K, A> e : map2.entrySet()) map1.merge(e.getKey(), e.getValue(), downstreamMerger); }; if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return (PartialCollector<Map<K, A>, M>) new PartialCollector<>((Supplier<Map<K, A>>) mapFactory, merger, Function.identity(), ID_CHARACTERISTICS); } Function<A, D> downstreamFinisher = downstream.finisher(); return new PartialCollector<>((Supplier<Map<K, A>>) mapFactory, merger, map -> { map.replaceAll((k, v) -> ((Function<A, A>) downstreamFinisher).apply(v)); return (M) map; }, NO_CHARACTERISTICS); }
CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Set<Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.characteristics = characteristics; }
/** * Adapts a {@code Collector} to perform an additional finishing * transformation. For example, one could adapt the {@link #toList()} * collector to always produce an immutable list with: * <pre>{@code * List<String> people * = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList)); * }</pre> * * @param <T> the type of the input elements * @param <A> intermediate accumulation type of the downstream collector * @param <R> result type of the downstream collector * @param <RR> result type of the resulting collector * @param downstream a collector * @param finisher a function to be applied to the final result of the downstream collector * @return a collector which performs the action of the downstream collector, * followed by an additional finishing step */ public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(final Collector<T, A, R> downstream, final Function<R, RR> finisher) { Objects.requireNonNull(finisher); final Function<A, R> downstreamFinisher = downstream.finisher(); final Function<A, RR> thenFinisher = new Function<A, RR>() { @Override public RR apply(A t) { return finisher.apply(downstreamFinisher.apply(t)); } }; Set<Collector.Characteristics> characteristics = downstream.characteristics(); if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) { if (characteristics.size() == 1) characteristics = Collectors.CH_NOID; else { characteristics = EnumSet.copyOf(characteristics); characteristics.remove(Collector.Characteristics.IDENTITY_FINISH); characteristics = Collections.unmodifiableSet(characteristics); } } return new CollectorImpl<>(downstream.supplier(), downstream.accumulator(), downstream.combiner(), thenFinisher, characteristics); }
/** * {@inheritDoc} * * <p> * If special <a * href="package-summary.html#ShortCircuitReduction">short-circuiting * collector</a> is passed, this operation becomes short-circuiting as well. */ @Override public <R, A> R collect(Collector<? super T, A, R> collector) { Predicate<A> finished = finished(collector); if (finished != null) { BiConsumer<A, ? super T> acc = collector.accumulator(); BinaryOperator<A> combiner = collector.combiner(); Spliterator<T> spliterator = spliterator(); if (!isParallel()) { A a = collector.supplier().get(); if (!finished.test(a)) { try { // forEachRemaining can be much faster // and take much less memory than tryAdvance for certain // spliterators spliterator.forEachRemaining(e -> { acc.accept(a, e); if (finished.test(a)) throw new CancelException(); }); } catch (CancelException ex) { // ignore } } return collector.finisher().apply(a); } Spliterator<A> spltr; if (!spliterator.hasCharacteristics(Spliterator.ORDERED) || collector.characteristics().contains(Characteristics.UNORDERED)) { spltr = new UnorderedCancellableSpliterator<>(spliterator, collector.supplier(), acc, combiner, finished); } else { spltr = new OrderedCancellableSpliterator<>(spliterator, collector.supplier(), acc, combiner, finished); } return collector.finisher().apply( new StreamEx<>(StreamSupport.stream(spltr, true), context).findFirst().get()); } return rawCollect(collector); }
/** * Adapts a {@code Collector} accepting elements of type {@code U} to one * accepting elements of type {@code T} by applying a flat mapping function * to each input element before accumulation. The flat mapping function maps * an input element to a {@link Stream stream} covering zero or more output * elements that are then accumulated downstream. Each mapped stream is * {@link java.util.stream.BaseStream#close() closed} after its contents * have been placed downstream. (If a mapped stream is {@code null} an empty * stream is used, instead.) * * <p> * This method is similar to {@code Collectors.flatMapping} method which * appears in JDK 9. However when downstream collector is * <a href="package-summary.html#ShortCircuitReduction">short-circuiting</a> * , this method will also return a short-circuiting collector. * * @param <T> the type of the input elements * @param <U> type of elements accepted by downstream collector * @param <A> intermediate accumulation type of the downstream collector * @param <R> result type of collector * @param mapper a function to be applied to the input elements, which * returns a stream of results * @param downstream a collector which will receive the elements of the * stream returned by mapper * @return a collector which applies the mapping function to the input * elements and provides the flat mapped results to the downstream * collector * @since 0.4.1 */ public static <T, U, A, R> Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper, Collector<? super U, A, R> downstream) { BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator(); Predicate<A> finished = finished(downstream); if (finished != null) { return new CancellableCollectorImpl<>(downstream.supplier(), (acc, t) -> { if (finished.test(acc)) return; try (Stream<? extends U> stream = mapper.apply(t)) { if (stream != null) { stream.spliterator().forEachRemaining(u -> { downstreamAccumulator.accept(acc, u); if (finished.test(acc)) throw new CancelException(); }); } } catch (CancelException ex) { // ignore } }, downstream.combiner(), downstream.finisher(), finished, downstream.characteristics()); } return Collector.of(downstream.supplier(), (acc, t) -> { try (Stream<? extends U> stream = mapper.apply(t)) { if (stream != null) { stream.spliterator().forEachRemaining(u -> downstreamAccumulator.accept(acc, u)); } } }, downstream.combiner(), downstream.finisher(), downstream.characteristics().toArray(new Characteristics[0])); }
BaseCollector(Supplier<A> supplier, BiConsumer<A, A> merger, Function<A, R> finisher, Set<Characteristics> characteristics) { this.supplier = supplier; this.merger = merger; this.finisher = finisher; this.characteristics = characteristics; }
CancellableCollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Predicate<A> finished, Set<java.util.stream.Collector.Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.finished = finished; this.characteristics = characteristics; }
/** * Returns a {@link Collector} that accumulates the input elements into an {@link ImmutableSet}. * * @return The {@link Collector}. Will never be {@code null}. */ public static <T> Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> toSet() { BinaryOperator<ImmutableSet.Builder<T>> combiner = (first, second) -> first.addAll(second.build()); return Collector.of(ImmutableSet::builder, ImmutableSet.Builder::add, combiner, ImmutableSet.Builder::build, Characteristics.UNORDERED); }
public static <T, A, R, X extends Throwable> ThrowingCollector<T, A, R, X> of(Collector<T, A, R> collector) { Objects.requireNonNull(collector); return new ThrowingCollector<T, A, R, X>() { @Override public ThrowingSupplier<A, X> supplier() { return collector.supplier()::get; } @Override public ThrowingBiConsumer<A, T, X> accumulator() { return collector.accumulator()::accept; } @Override public ThrowingBinaryOperator<A, X> combiner() { return collector.combiner()::apply; } @Override public ThrowingFunction<A, R, X> finisher() { return collector.finisher()::apply; } @Override public Set<Characteristics> characteristics() { return collector.characteristics(); } }; }
public static <T> Collector<T, ?, Multiset<T>> toMultiSet( final Supplier<Multiset<T>> supplier ){ return Collector.of( supplier, ( set, t ) -> set.add( t ), ( l, r ) -> { l.addAll( r ); return l; }, l -> l, Characteristics.IDENTITY_FINISH ); }
public static <T extends Comparable<?>> Collector<T, ImmutableSortedSet.Builder<T>, ImmutableSortedSet<T>> toImmutableSortedSetReversed() { return Collector.of( ImmutableSortedSet::<T> reverseOrder, ImmutableSortedSet.Builder<T>::add, (l, r) -> l.addAll(r.build()), ImmutableSortedSet.Builder<T>::build, Characteristics.UNORDERED); }
public static <T> Collector<T, ?, ImmutableSortedSet<T>> toImmutableSortedSet( final Supplier<Builder<T>> supplier ){ return Collector.of( supplier, ImmutableSortedSet.Builder<T>::add, (l, r) -> l.addAll(r.build()), ImmutableSortedSet.Builder<T>::build, Characteristics.UNORDERED); }
public static <T> Collector<T, ?, ImmutableSortedSet<T>> toImmutableSortedSet( final Comparator<T> supplier ){; return Collector.of( () -> ImmutableSortedSet.orderedBy( supplier ), ImmutableSortedSet.Builder<T>::add, (l, r) -> l.addAll(r.build()), ImmutableSortedSet.Builder<T>::build, Characteristics.UNORDERED); }
public static <T, K, V> Collector<T,?,BiMap<K,V>> toBiMap( final Supplier<BiMap<K,V>> supplier, final Function<T,K> keyFunction, final Function<T,V> valueFunction ){ return Collector.of( supplier, (map, value) -> map.put( keyFunction.apply( value ), valueFunction.apply( value )), (l, r) -> { l.putAll( r ); return l;}, map -> map, Characteristics.IDENTITY_FINISH ); }
public static <T, R, C, V> Collector<T,?,Table<R,C,V>> toTable( final Function<T,R> rowFunction, final Function<T,C> columnFunction, final Function<T,V> valueFunction ){ return Collector.of( HashBasedTable::<R,C,V> create, (table, value ) -> table.put( rowFunction.apply( value ), columnFunction.apply( value ), valueFunction.apply( value )), (l, r) -> { l.putAll( r ); return l; }, table -> table, Characteristics.IDENTITY_FINISH); }
public static <T, R, C, V> Collector<T,?,Table<R,C,V>> toTable( final Supplier<Table<R,C,V>> supplier, final Function<T,R> rowFunction, final Function<T,C> columnFunction, final Function<T,V> valueFunction ){ return Collector.of( supplier, (table, value ) -> table.put( rowFunction.apply( value ), columnFunction.apply( value ), valueFunction.apply( value )), (l, r) -> { l.putAll( r ); return l; }, table -> table, Characteristics.IDENTITY_FINISH); }
public static <T, M extends Multimap<K,V>, K, V> Collector<T, ?, M> toMultimap( final Supplier<M> supplier, final Function<T,K> keyFunction, final Function<T,V> valueFunction ){ return Collector.of( supplier, (map, value) -> map.put( keyFunction.apply( value ), valueFunction.apply( value )), (l, r) -> { l.putAll( r ); return l; }, map -> map, Characteristics.IDENTITY_FINISH ); }
public static <T, K, V, M extends Multimap<K,V>> Collector<T,?,M> toMultimapFromIterable( final Supplier<M> supplier, final Function<T,K> keyFunction, final Function<T, ? extends Iterable<V>> valueFunction ){ return Collector.of( supplier, (map, value) -> map.putAll( keyFunction.apply( value ), valueFunction.apply( value )), (l, r) -> { l.putAll( r ); return l; }, map -> map, Characteristics.IDENTITY_FINISH ); }
private CollectorTester(Collector<T, A, R> collectorToTest, List<Stream<T>> inputs, List<Matcher<? super R>> results, Matcher<Iterable<? extends Characteristics>> expectedCharacteristics) { this.collectorToTest = collectorToTest; this.inputs = inputs; this.results = results; this.expectedCharacteristics = expectedCharacteristics; }
@Override public CollectorTesterDSL3<T, A, R> havingCharacteristics( Characteristics... expectedCharacteristics) { this.expectedCharacteristics = TestSuite.DSL .<Collector.Characteristics> containsInAnyOrder(expectedCharacteristics); return this; }
/** Returns a collector that accumulates the the input elements into a new NBT list. */ public static <T> Collector<T, NBTTagList, NBTTagList> toList() { return Collector.of(NBTTagList::new, (list, element) -> list.appendTag(createTag(element)), (left, right) -> { for (NBTBase tag : iterate(right)) left.appendTag(tag); return left; }, Characteristics.IDENTITY_FINISH); }
/** Returns a collector that accumulates the the input NBT tags into a new NBT list. */ public static <T> Collector<T, NBTTagCompound, NBTTagCompound> toCompound( Function<T, String> keyMapper, Function<T, NBTBase> tagMapper) { return Collector.of(NBTTagCompound::new, (compound, element) -> compound.setTag(keyMapper.apply(element), tagMapper.apply(element)), (left, right) -> { for (String key : right.getKeySet()) left.setTag(key, right.getTag(key)); return left; }, Characteristics.IDENTITY_FINISH); }
/** * Returns a {@link Collector} that accumulates all the given {@link JsonElement}s into a new {@link JsonArray}. * * @return * A {@link Collector} that accumulates all the given {@link JsonElement}s into a new {@link JsonArray}. */ public static <T extends JsonElement> Collector<T, JsonArray, JsonArray> toJsonArray() { return Collector.of(JsonArray::new, (array, element) -> array.add(element), (one, other) -> { one.addAll(other); return one; }, Characteristics.IDENTITY_FINISH); }
CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Set<Characteristics> characteristics) { this(supplier, accumulator, combiner, (Function<A, R>) IDENTITY_FINISHER, characteristics); }
@Override public Set<com.landawn.abacus.util.stream.Collector.Characteristics> characteristics() { return characteristics; }
@Override public Set<Characteristics> characteristics() { return characteristics; }
PartialCollector(Supplier<A> supplier, BiConsumer<A, A> merger, Function<A, R> finisher, Set<Characteristics> characteristics) { super(supplier, merger, finisher, characteristics); }
<T> Collector<T, A, R> asRef(BiConsumer<A, T> accumulator) { return Collector.of(supplier, accumulator, combiner(), finisher, characteristics .toArray(new Characteristics[0])); }
IntCollectorImpl(Supplier<A> supplier, ObjIntConsumer<A> intAccumulator, BiConsumer<A, A> merger, Function<A, R> finisher, Set<Characteristics> characteristics) { super(supplier, merger, finisher, characteristics); this.intAccumulator = intAccumulator; }
LongCollectorImpl(Supplier<A> supplier, ObjLongConsumer<A> longAccumulator, BiConsumer<A, A> merger, Function<A, R> finisher, Set<Characteristics> characteristics) { super(supplier, merger, finisher, characteristics); this.longAccumulator = longAccumulator; }
DoubleCollectorImpl(Supplier<A> supplier, ObjDoubleConsumer<A> doubleAccumulator, BiConsumer<A, A> merger, Function<A, R> finisher, Set<Characteristics> characteristics) { super(supplier, merger, finisher, characteristics); this.doubleAccumulator = doubleAccumulator; }
/** * Returns a {@code Collector} that accumulates the input elements into a * new ImmutableSet. * * @param <T> type * @return a {@code Collector} which collects all the input elements into a * {@code ImmutableSet} */ public static <T> Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> toImmutableSet() { return Collector.of(ImmutableSet.Builder<T>::new, ImmutableSet.Builder<T>::add, (s, r) -> s.addAll(r.build()), ImmutableSet.Builder<T>::build, Characteristics.UNORDERED); }
/** * Returns a {@code Map} whose keys are the values resulting from applying * the classification function to the input elements, and whose * corresponding values are the result of reduction of the input elements * which map to the associated key under the classification function. * * <p> * There are no guarantees on the type, mutability or serializability of the * {@code Map} objects returned. * * <p> * This is a <a href="package-summary.html#StreamOps">terminal</a> * operation. * * @param <K> the type of the keys * @param <D> the result type of the downstream reduction * @param classifier the classifier function mapping input elements to keys * @param downstream a {@code Collector} implementing the downstream * reduction * @return a {@code Map} containing the results of the group-by operation * * @see #groupingBy(Function) * @see Collectors#groupingBy(Function, Collector) * @see Collectors#groupingByConcurrent(Function, Collector) */ public <K, D> Map<K, D> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, ?, D> downstream) { if (isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED)) return rawCollect(Collectors.groupingByConcurrent(classifier, downstream)); return rawCollect(Collectors.groupingBy(classifier, downstream)); }
/** * Returns a {@code Map} whose keys are the values resulting from applying * the classification function to the input elements, and whose * corresponding values are the result of reduction of the input elements * which map to the associated key under the classification function. * * <p> * The {@code Map} will be created using the provided factory function. * * <p> * This is a <a href="package-summary.html#StreamOps">terminal</a> * operation. * * @param <K> the type of the keys * @param <D> the result type of the downstream reduction * @param <M> the type of the resulting {@code Map} * @param classifier the classifier function mapping input elements to keys * @param mapFactory a function which, when called, produces a new empty * {@code Map} of the desired type * @param downstream a {@code Collector} implementing the downstream * reduction * @return a {@code Map} containing the results of the group-by operation * * @see #groupingBy(Function) * @see Collectors#groupingBy(Function, Supplier, Collector) * @see Collectors#groupingByConcurrent(Function, Supplier, Collector) */ @SuppressWarnings("unchecked") public <K, D, M extends Map<K, D>> M groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, ?, D> downstream) { if (isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED) && mapFactory.get() instanceof ConcurrentMap) return (M) rawCollect(Collectors.groupingByConcurrent(classifier, (Supplier<ConcurrentMap<K, D>>) mapFactory, downstream)); return rawCollect(Collectors.groupingBy(classifier, mapFactory, downstream)); }
/** * Returns a {@code Collector} which just ignores the input and calls the * provided supplier once to return the output. * * @param <T> the type of input elements * @param <U> the type of output * @param supplier the supplier of the output * @return a {@code Collector} which just ignores the input and calls the * provided supplier once to return the output. */ private static <T, U> Collector<T, ?, U> empty(Supplier<U> supplier) { return new CancellableCollectorImpl<>(() -> NONE, (acc, t) -> { // empty }, selectFirst(), acc -> supplier.get(), acc -> true, EnumSet.of(Characteristics.UNORDERED, Characteristics.CONCURRENT)); }
/** * Adapts a {@code Collector} to perform an additional finishing * transformation. * * <p> * Unlike {@link Collectors#collectingAndThen(Collector, Function)} this * method returns a * <a href="package-summary.html#ShortCircuitReduction">short-circuiting * collector</a> if the downstream collector is short-circuiting. * * @param <T> the type of the input elements * @param <A> intermediate accumulation type of the downstream collector * @param <R> result type of the downstream collector * @param <RR> result type of the resulting collector * @param downstream a collector * @param finisher a function to be applied to the final result of the * downstream collector * @return a collector which performs the action of the downstream * collector, followed by an additional finishing step * @see Collectors#collectingAndThen(Collector, Function) * @since 0.4.0 */ public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(Collector<T, A, R> downstream, Function<R, RR> finisher) { Predicate<A> finished = finished(downstream); if (finished != null) { return new CancellableCollectorImpl<>(downstream.supplier(), downstream.accumulator(), downstream .combiner(), downstream.finisher().andThen(finisher), finished, downstream.characteristics() .contains(Characteristics.UNORDERED) ? UNORDERED_CHARACTERISTICS : NO_CHARACTERISTICS); } return Collectors.collectingAndThen(downstream, finisher); }
/** * Returns a {@code Collector} which performs downstream reduction if all * elements satisfy the {@code Predicate}. The result is described as an * {@code Optional<R>}. * * <p> * The resulting collector returns an empty optional if at least one input * element does not satisfy the predicate. Otherwise it returns an optional * which contains the result of the downstream collector. * * <p> * This method returns a * <a href="package-summary.html#ShortCircuitReduction">short-circuiting * collector</a>: it may not process all the elements if some of items don't * satisfy the predicate or if downstream collector is a short-circuiting * collector. * * <p> * It's guaranteed that the downstream collector is not called for elements * which don't satisfy the predicate. * * @param <T> the type of input elements * @param <A> intermediate accumulation type of the downstream collector * @param <R> result type of the downstream collector * @param predicate a non-interfering, stateless predicate to checks whether * collector should proceed with element * @param downstream a {@code Collector} implementing the downstream * reduction * @return a {@code Collector} witch performs downstream reduction if all * elements satisfy the predicate * @see Stream#allMatch(Predicate) * @see AbstractStreamEx#dropWhile(Predicate) * @see AbstractStreamEx#takeWhile(Predicate) */ public static <T, A, R> Collector<T, ?, Optional<R>> ifAllMatch(Predicate<T> predicate, Collector<T, A, R> downstream) { Predicate<A> finished = finished(downstream); Supplier<A> supplier = downstream.supplier(); BiConsumer<A, T> accumulator = downstream.accumulator(); BinaryOperator<A> combiner = downstream.combiner(); return new CancellableCollectorImpl<>( () -> new PairBox<>(supplier.get(), Boolean.TRUE), (acc, t) -> { if (acc.b && predicate.test(t)) { accumulator.accept(acc.a, t); } else { acc.b = Boolean.FALSE; } }, (acc1, acc2) -> { if (acc1.b && acc2.b) { acc1.a = combiner.apply(acc1.a, acc2.a); } else { acc1.b = Boolean.FALSE; } return acc1; }, acc -> acc.b ? Optional.of(downstream.finisher().apply(acc.a)) : Optional.empty(), finished == null ? acc -> !acc.b : acc -> !acc.b || finished.test(acc.a), downstream.characteristics().contains(Characteristics.UNORDERED) ? UNORDERED_CHARACTERISTICS : NO_CHARACTERISTICS); }
/** * Returns a {@link Map} where elements of this stream with the same key are * grouped together. The resulting {@code Map} keys are the keys of this * stream entries and the corresponding values are combined using the * provided downstream collector. * * <p> * There are no guarantees on the type, mutability, serializability, or * thread-safety of the {@code Map} object returned. If more control over * the returned {@code Map} is required, use * {@link #grouping(Supplier, Collector)}. * * <p> * This is a <a href="package-summary.html#StreamOps">terminal</a> * operation. * * @param <A> the intermediate accumulation type of the downstream collector * @param <D> the result type of the downstream reduction * @param downstream a {@code Collector} implementing the downstream * reduction * @return a {@code Map} containing the elements of this stream * @see Collectors#groupingBy(Function, Collector) */ public <A, D> Map<K, D> grouping(Collector<? super V, A, D> downstream) { Function<Entry<K, V>, K> keyMapper = Entry::getKey; Collector<Entry<K, V>, ?, D> mapping = Collectors.mapping(Entry::getValue, downstream); if (isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED)) { return collect(Collectors.groupingByConcurrent(keyMapper, mapping)); } return collect(Collectors.groupingBy(keyMapper, mapping)); }