com.tangosol.util.stream.RemoteCollectors Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.util.stream;
import com.tangosol.internal.util.DoubleSummaryStatistics;
import com.tangosol.internal.util.IntSummaryStatistics;
import com.tangosol.internal.util.LongSummaryStatistics;
import com.tangosol.internal.util.collection.PortableCollection;
import com.tangosol.internal.util.collection.PortableList;
import com.tangosol.internal.util.collection.PortableMap;
import com.tangosol.internal.util.collection.PortableSet;
import com.tangosol.internal.util.collection.PortableSortedSet;
import com.tangosol.internal.util.invoke.Lambdas;
import com.tangosol.internal.util.stream.collectors.AveragingDoubleCollector;
import com.tangosol.internal.util.stream.collectors.AveragingIntCollector;
import com.tangosol.internal.util.stream.collectors.AveragingLongCollector;
import com.tangosol.internal.util.stream.collectors.BiReducingCollector;
import com.tangosol.internal.util.stream.collectors.CollectingAndThenCollector;
import com.tangosol.internal.util.stream.collectors.CollectionCollector;
import com.tangosol.internal.util.stream.collectors.GroupingByCollector;
import com.tangosol.internal.util.stream.collectors.MapCollector;
import com.tangosol.internal.util.stream.collectors.MappingCollector;
import com.tangosol.internal.util.stream.collectors.ReducingCollector;
import com.tangosol.internal.util.stream.collectors.SummarizingDoubleCollector;
import com.tangosol.internal.util.stream.collectors.SummarizingIntCollector;
import com.tangosol.internal.util.stream.collectors.SummarizingLongCollector;
import com.tangosol.internal.util.stream.collectors.SummingDoubleCollector;
import com.tangosol.internal.util.stream.collectors.SummingIntCollector;
import com.tangosol.internal.util.stream.collectors.SummingLongCollector;
import com.tangosol.util.InvocableMap;
import com.tangosol.util.SimpleHolder;
import com.tangosol.util.SortedBag;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.SafeComparator;
import com.tangosol.util.function.Remote;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
/**
* Static factory for various {@link RemoteCollector}s that can be executed in
* parallel in a distributed environment.
*
* @author as 2014.10.01
* @since 12.2.1
*
* @see RemoteCollector
* @see java.util.stream.Collectors
*/
public abstract class RemoteCollectors
{
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code Collection}, in encounter order. The {@code Collection} is
* created by the provided factory.
*
* @param the type of the input elements
* @param the type of the resulting {@code Collection}
* @param supplier a {@code Supplier} which returns a new, empty
* {@code Collection} of the appropriate type
*
* @return a {@code Collector} which collects all the input elements into a
* {@code Collection}, in encounter order
*/
public static > RemoteCollector toCollection(Remote.Supplier supplier)
{
return new CollectionCollector<>(supplier);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code List}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code List} returned; if more
* control over the returned {@code List} is required, use {@link
* #toCollection(Remote.Supplier)}.
*
* @param the type of the input elements
*
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static RemoteCollector> toList()
{
return toCollection(PortableList::new);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedBag}.
*
* @param the type of the input elements
*
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static RemoteCollector> toSortedBag()
{
return toSortedBag(SafeComparator.INSTANCE);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedBag}.
*
* @param the type of the input elements
* @param comparator a comparator for type T
*
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static RemoteCollector> toSortedBag(Comparator super T> comparator)
{
Remote.Supplier> supplier = () -> new SortedBag<>(comparator);
return toCollection(() -> new PortableCollection<>(supplier));
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedBag}.
*
* @param the type of the input elements
* @param comparator a comparator for type T
*
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static RemoteCollector> toSortedBag(Remote.Comparator super T> comparator)
{
Remote.Supplier> supplier = () -> new SortedBag<>(comparator);
return toCollection(() -> new PortableCollection<>(supplier));
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code Set}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code Set} returned; if more
* control over the returned {@code Set} is required, use {@link
* #toCollection(Remote.Supplier)}.
*
* This is an {@link RemoteCollector.Characteristics#UNORDERED unordered}
* Collector.
*
* @param the type of the input elements
*
* @return a {@code Collector} which collects all the input elements into a
* {@code Set}
*/
public static RemoteCollector> toSet()
{
return toCollection(PortableSet::new);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedSet}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code SortedSet} returned;
* if more control over the returned {@code SortedSet} is required, use
* {@link #toCollection(Remote.Supplier)}.
*
* This is an {@link RemoteCollector.Characteristics#UNORDERED unordered}
* Collector.
*
* @param the type of the input elements
*
* @return a {@code Collector} which collects all the input elements into a
* {@code Set}
*/
public static RemoteCollector> toSortedSet()
{
return toCollection(PortableSortedSet::new);
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedSet}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code SortedSet} returned;
* if more control over the returned {@code SortedSet} is required, use
* {@link #toCollection(Remote.Supplier)}.
*
* This is an {@link RemoteCollector.Characteristics#UNORDERED unordered}
* Collector.
*
* @param the type of the input elements
* @param comparator a comparator for type T
*
* @return a {@code Collector} which collects all the input elements into a
* {@code Set}
*/
public static RemoteCollector> toSortedSet(Comparator super T> comparator)
{
return toCollection(() -> new PortableSortedSet<>(comparator));
}
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code SortedSet}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code SortedSet} returned;
* if more control over the returned {@code SortedSet} is required, use
* {@link #toCollection(Remote.Supplier)}.
*
* This is an {@link RemoteCollector.Characteristics#UNORDERED unordered}
* Collector.
*
* @param the type of the input elements
* @param comparator a comparator for type T
*
* @return a {@code Collector} which collects all the input elements into a
* {@code Set}
*/
public static RemoteCollector> toSortedSet(Remote.Comparator super T> comparator)
{
return toCollection(() -> new PortableSortedSet<>(comparator));
}
/**
* Adapts a {@code Collector} accepting elements of type {@code U} to one
* accepting elements of type {@code T} by applying a mapping function to
* each input element before accumulation.
*
* @param the type of the input elements
* @param type of elements accepted by downstream collector
* @param intermediate accumulation type of the downstream
* collector
* @param result type of collector
* @param mapper a function to be applied to the input elements
* @param downstream a collector which will accept mapped values
*
* @return a collector which applies the mapping function to the input
* elements and provides the mapped results to the downstream collector
*
* @apiNote The {@code mapping()} collectors are most useful when used in a
* multi-level reduction, such as downstream of a {@code groupingBy} or
* {@code partitioningBy}. For example, given a stream of {@code Person},
* to accumulate the set of last names in each city:
* {@code
* Map> lastNamesByCity
* = people.stream().collect(groupingBy(Person::getCity,
* mapping(Person::getLastName,
* toSet())));
* }
*/
public static
RemoteCollector mapping(Remote.Function super T, ? extends U> mapper,
RemoteCollector super U, A, R> downstream)
{
return new MappingCollector(mapper, downstream);
}
/**
* 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:
* {@code
* List people
* = people.stream().collect(collectingAndThen(toList(),
* Collections::unmodifiableList));
* }
*
* @param the type of the input elements
* @param intermediate accumulation type of the downstream
* collector
* @param result type of the downstream collector
* @param 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
RemoteCollector collectingAndThen(RemoteCollector downstream,
Remote.Function finisher)
{
return new CollectingAndThenCollector<>(downstream, finisher);
}
/**
* Returns a {@code Collector} accepting elements of type {@code T} that
* counts the number of input elements. If no elements are present, the
* result is 0.
*
* @param the type of the input elements
*
* @return a {@code Collector} that counts the input elements
*
* @implSpec This produces a result equivalent to:
* {@code
* reducing(0L, e -> 1L, Long::sum)
* }
*/
public static RemoteCollector counting()
{
return reducing(0L, e -> 1L, Long::sum);
}
/**
* Returns a {@code Collector} that produces the minimal element according
* to a given {@code Comparator}, described as an {@code Optional}.
*
* @param the type of the input elements
* @param comparator a {@code Comparator} for comparing elements
*
* @return a {@code Collector} that produces the minimal value
*
* @implSpec This produces a result equivalent to:
* {@code
* reducing(Remote.BinaryOperator.minBy(comparator))
* }
*/
public static RemoteCollector> minBy(Remote.Comparator super T> comparator)
{
return reducing(Remote.BinaryOperator.minBy(comparator));
}
/**
* Returns a {@code Collector} that produces the maximal element according
* to a given {@code Comparator}, described as an {@code Optional}.
*
* @param the type of the input elements
* @param comparator a {@code Comparator} for comparing elements
*
* @return a {@code Collector} that produces the maximal value
*
* @implSpec This produces a result equivalent to:
* {@code
* reducing(BinaryOperator.maxBy(comparator))
* }
*/
public static RemoteCollector> maxBy(Remote.Comparator super T> comparator)
{
return reducing(Remote.BinaryOperator.maxBy(comparator));
}
/**
* Returns a {@code Collector} that produces the sum of a integer-valued
* function applied to the input elements. If no elements are present, the
* result is 0.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be summed
*
* @return a {@code Collector} that produces the sum of a derived property
*/
public static RemoteCollector
summingInt(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToIntFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).intValue()
: ex.extract((U) t).intValue();
return new SummingIntCollector<>(mapper);
}
/**
* Returns a {@code Collector} that produces the sum of a long-valued
* function applied to the input elements. If no elements are present, the
* result is 0.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be summed
*
* @return a {@code Collector} that produces the sum of a derived property
*/
public static RemoteCollector
summingLong(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToLongFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).longValue()
: ex.extract((U) t).longValue();
return new SummingLongCollector<>(mapper);
}
/**
* Returns a {@code Collector} that produces the sum of a double-valued
* function applied to the input elements. If no elements are present, the
* result is 0.
*
* The sum returned can vary depending upon the order in which values are
* recorded, due to accumulated rounding error in addition of values of
* differing magnitudes. Values sorted by increasing absolute magnitude tend
* to yield more accurate results. If any recorded value is a {@code NaN}
* or the sum is at any point a {@code NaN} then the sum will be {@code
* NaN}.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be summed
*
* @return a {@code Collector} that produces the sum of a derived property
*/
public static RemoteCollector
summingDouble(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToDoubleFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).doubleValue()
: ex.extract((U) t).doubleValue();
return new SummingDoubleCollector<>(mapper);
}
/**
* Returns a {@code Collector} that produces the arithmetic mean of an
* integer-valued function applied to the input elements. If no elements
* are present, the result is 0.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be summed
*
* @return a {@code Collector} that produces the sum of a derived property
*/
public static RemoteCollector
averagingInt(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToIntFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).intValue()
: ex.extract((U) t).intValue();
return new AveragingIntCollector<>(mapper);
}
/**
* Returns a {@code Collector} that produces the arithmetic mean of a
* long-valued function applied to the input elements. If no elements are
* present, the result is 0.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be averaged
*
* @return a {@code Collector} that produces the average of a derived property
*/
public static RemoteCollector
averagingLong(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToLongFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).longValue()
: ex.extract((U) t).longValue();
return new AveragingLongCollector<>(mapper);
}
/**
* Returns a {@code Collector} that produces the arithmetic mean of a
* double-valued function applied to the input elements. If no elements are
* present, the result is 0.
*
* The average returned can vary depending upon the order in which values
* are recorded, due to accumulated rounding error in addition of values of
* differing magnitudes. Values sorted by increasing absolute magnitude tend
* to yield more accurate results. If any recorded value is a {@code NaN}
* or the sum is at any point a {@code NaN} then the average will be {@code
* NaN}.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param extractor a function extracting the property to be summed
*
* @return a {@code Collector} that produces the sum of a derived property
*
* @implNote The {@code double} format can represent all consecutive
* integers in the range -253 to 253. If the pipeline
* has more than 253 values, the divisor in the average
* computation will saturate at 253, leading to additional
* numerical errors.
*/
public static RemoteCollector
averagingDouble(ValueExtractor super U, ? extends Number> extractor)
{
ValueExtractor super U, ? extends Number> ex = Lambdas.ensureRemotable(extractor);
Remote.ToDoubleFunction super T> mapper =
t -> t instanceof InvocableMap.Entry
? ((InvocableMap.Entry, ?>) t).extract(ex).doubleValue()
: ex.extract((U) t).doubleValue();
return new AveragingDoubleCollector<>(mapper);
}
/**
* Returns a {@code Collector} which performs a reduction of its input
* elements under a specified {@code BinaryOperator} using the provided
* identity.
*
* @param element type for the input and output of the reduction
* @param identity the identity value for the reduction (also, the value
* that is returned when there are no input elements)
* @param op a {@code BinaryOperator} used to reduce the input
* elements
*
* @return a {@code Collector} which implements the reduction operation
*
* @apiNote The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or {@code
* partitioningBy}. To perform a simple reduction on a stream, use {@link
* RemoteStream#reduce(Object, BinaryOperator)}} instead.
* @see #reducing(Remote.BinaryOperator)
* @see #reducing(Object, Remote.Function, Remote.BinaryOperator)
*/
public static RemoteCollector, T> reducing(T identity, Remote.BinaryOperator op)
{
return new ReducingCollector<>(identity, op);
}
/**
* Returns a {@code Collector} which performs a reduction of its input
* elements under a specified {@code BinaryOperator}. The result is
* described as an {@code Optional}.
*
* @param element type for the input and output of the reduction
* @param op a {@code BinaryOperator} used to reduce the input elements
*
* @return a {@code Collector} which implements the reduction operation
*
* @apiNote The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or {@code
* partitioningBy}. To perform a simple reduction on a stream, use {@link
* RemoteStream#reduce(BinaryOperator)} instead.
*
* For example, given a stream of {@code Person}, to calculate tallest
* person in each city:
*
{@code
* Comparator byHeight = Comparator.comparing(Person::getHeight);
* Map tallestByCity
* = people.stream().collect(groupingBy(Person::getCity,
* reducing(BinaryOperator.maxBy(byHeight))));
* }
* @see #reducing(Object, Remote.BinaryOperator)
* @see #reducing(Object, Remote.Function, Remote.BinaryOperator)
*/
public static RemoteCollector> reducing(Remote.BinaryOperator op)
{
return collectingAndThen(reducing(null, op), Optional::ofNullable);
}
/**
* Returns a {@code Collector} which performs a reduction of its input
* elements under a specified mapping function and {@code BinaryOperator}.
* This is a generalization of {@link #reducing(Object, Remote.BinaryOperator)}
* which allows a transformation of the elements before reduction.
*
* @param the type of the input elements
* @param the type of the mapped values
* @param identity the identity value for the reduction (also, the value
* that is returned when there are no input elements)
* @param mapper a mapping function to apply to each input value
* @param op a {@code BinaryOperator} used to reduce the mapped
* values
*
* @return a {@code Collector} implementing the map-reduce operation
*
* @apiNote The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or {@code
* partitioningBy}. To perform a simple map-reduce on a stream, use {@link
* RemoteStream#map(Function)} and {@link RemoteStream#reduce(Object, BinaryOperator)}
* instead.
*
* For example, given a stream of {@code Person}, to calculate the
* longest last name of residents in each city:
*
{@code
* Comparator byLength = Comparator.comparing(String::length);
* Map longestLastNameByCity
* = people.stream().collect(groupingBy(Person::getCity,
* reducing(Person::getLastName,
* BinaryOperator.maxBy(byLength))));
* }
* @see #reducing(Object, Remote.BinaryOperator)
* @see #reducing(Remote.BinaryOperator)
*/
public static RemoteCollector reducing(U identity,
Remote.Function super T, ? extends U> mapper,
Remote.BinaryOperator op)
{
return mapping(mapper, reducing(identity, op));
}
/**
* Returns a {@code Collector} which performs a reduction of its input
* elements under a specified mapping function and {@code BinaryOperator}.
* This is a generalization of {@link #reducing(Object, Remote.BinaryOperator)}
* which allows a transformation of the elements before reduction.
*
* @param the type of the input elements
* @param the type of the mapped values
* @param identity the identity value for the reduction (also, the value
* that is returned when there are no input elements)
* @param mapper a mapping function to apply to each input value
* @param op a {@code BinaryOperator} used to reduce the mapped
* values
*
* @return a {@code Collector} implementing the map-reduce operation
*
* @apiNote The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or {@code
* partitioningBy}. To perform a simple map-reduce on a stream, use {@link
* RemoteStream#map(Function)} and {@link RemoteStream#reduce(Object, BinaryOperator)}
* instead.
*
* For example, given a stream of {@code Person}, to calculate the
* longest last name of residents in each city:
*
{@code
* Comparator byLength = Comparator.comparing(String::length);
* Map longestLastNameByCity
* = people.stream().collect(groupingBy(Person::getCity,
* reducing(Person::getLastName,
* BinaryOperator.maxBy(byLength))));
* }
* @see #reducing(Object, Remote.BinaryOperator)
* @see #reducing(Remote.BinaryOperator)
*/
public static RemoteCollector reducing(U identity,
Remote.BiFunction super U, ? super T, ? extends U> mapper,
Remote.BinaryOperator op)
{
return new BiReducingCollector<>(identity, mapper, op);
}
/**
* Returns a {@code Collector} implementing a "group by" operation on input
* elements of type {@code T}, grouping elements according to a
* classification function, and returning the results in a {@code Map}.
*
* The classification function maps elements to some key type {@code K}.
* The collector produces a {@code Map>} whose keys are the
* values resulting from applying the classification function to the input
* elements, and whose corresponding values are {@code List}s containing the
* input elements which map to the associated key under the classification
* function.
*
* There are no guarantees on the type, mutability, serializability, or
* thread-safety of the {@code Map} or {@code List} objects returned.
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param the type of the keys
* @param classifier the classifier function mapping input elements to keys
*
* @return a {@code Collector} implementing the group-by operation
*
* @implSpec This produces a result similar to:
* {@code
* groupingBy(classifier, toList());
* }
* @see #groupingBy(ValueExtractor, RemoteCollector)
* @see #groupingBy(ValueExtractor, Remote.Supplier, RemoteCollector)
*/
public static RemoteCollector>>
groupingBy(ValueExtractor super U, ? extends K> classifier)
{
return groupingBy(classifier, toList());
}
/**
* 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 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}.
*
* There are no guarantees on the type, mutability, serializability, or
* thread-safety of the {@code Map} returned.
*
* For example, to compute the set of last names of people in each city:
*
{@code
* Map> namesByCity
* = people.stream().collect(groupingBy(Person::getCity,
* mapping(Person::getLastName,
* toSet())));
* }
*
* @param the type of the stream elements
* @param the type of the objects to extract from, which should be
* either the same as {@code T}, or the key or value type
* if the {@code T} is {@code InvocableMap.Entry}
* @param the type of the keys
* @param the intermediate accumulation type of the downstream
* collector
* @param the result type of the downstream reduction
* @param classifier a classifier function mapping input elements to keys
* @param downstream a {@code Collector} implementing the downstream
* reduction
*
* @return a {@code Collector} implementing the cascaded group-by operation
*
* @see #groupingBy(ValueExtractor)
* @see #groupingBy(ValueExtractor, Remote.Supplier, RemoteCollector)
*/
public static
RemoteCollector> groupingBy(ValueExtractor super U, ? extends K> classifier,
RemoteCollector super T, A, D> downstream)
{
return groupingBy(classifier, HashMap::new, downstream);
}
/**
* 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.
*