com.annimon.stream.Stream Maven / Gradle / Ivy
package com.annimon.stream;
import java.io.Closeable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.annimon.stream.function.BiConsumer;
import com.annimon.stream.function.BiFunction;
import com.annimon.stream.function.BinaryOperator;
import com.annimon.stream.function.Consumer;
import com.annimon.stream.function.Function;
import com.annimon.stream.function.IntFunction;
import com.annimon.stream.function.Predicate;
import com.annimon.stream.function.Supplier;
import com.annimon.stream.function.ToDoubleFunction;
import com.annimon.stream.function.ToIntFunction;
import com.annimon.stream.function.ToLongFunction;
import com.annimon.stream.function.UnaryOperator;
import com.annimon.stream.internal.Compose;
import com.annimon.stream.internal.Operators;
import com.annimon.stream.internal.Params;
import com.annimon.stream.iterator.LazyIterator;
import com.annimon.stream.operator.ObjArray;
import com.annimon.stream.operator.ObjChunkBy;
import com.annimon.stream.operator.ObjConcat;
import com.annimon.stream.operator.ObjDistinct;
import com.annimon.stream.operator.ObjDistinctBy;
import com.annimon.stream.operator.ObjDropWhile;
import com.annimon.stream.operator.ObjFilter;
import com.annimon.stream.operator.ObjFlatMap;
import com.annimon.stream.operator.ObjFlatMapToDouble;
import com.annimon.stream.operator.ObjFlatMapToInt;
import com.annimon.stream.operator.ObjFlatMapToLong;
import com.annimon.stream.operator.ObjGenerate;
import com.annimon.stream.operator.ObjIterate;
import com.annimon.stream.operator.ObjLimit;
import com.annimon.stream.operator.ObjMap;
import com.annimon.stream.operator.ObjMapToDouble;
import com.annimon.stream.operator.ObjMapToInt;
import com.annimon.stream.operator.ObjMapToLong;
import com.annimon.stream.operator.ObjMerge;
import com.annimon.stream.operator.ObjPeek;
import com.annimon.stream.operator.ObjScan;
import com.annimon.stream.operator.ObjScanIdentity;
import com.annimon.stream.operator.ObjSkip;
import com.annimon.stream.operator.ObjSlidingWindow;
import com.annimon.stream.operator.ObjSorted;
import com.annimon.stream.operator.ObjTakeUntil;
import com.annimon.stream.operator.ObjTakeWhile;
import com.annimon.stream.operator.ObjZip;
/**
* A sequence of elements supporting aggregate operations.
*
* @param the type of the stream elements
*/
public class Stream implements Closeable {
static final Object NONE = new Object();
@SuppressWarnings({ "rawtypes" })
private static final Stream EMPTY = new Stream<>(Collections.emptyList());
/**
* Returns an empty stream.
*
* @param the type of the stream elements
* @return the new empty stream
*/
@SuppressWarnings("unchecked")
public static Stream empty() {
return EMPTY;
}
public static Stream just(final T value) {
return of(value);
}
/**
* If specified element is null, returns an empty {@code Stream},
* otherwise returns a {@code Stream} containing a single element.
*
* @param the type of the stream element
* @param element the element to be passed to stream if it is non-null
* @return the new stream
* @since 1.1.5
*/
public static Stream ofNullable(T element) {
return (element == null) ? Stream. empty() : Stream.of(element);
}
/**
* Creates a {@code Stream} from the specified values.
*
* @param the type of the stream elements
* @param elements the elements to be passed to stream
* @return the new stream
* @throws NullPointerException if {@code elements} is null
*/
@SafeVarargs
public static Stream of(final T... elements) {
if (elements == null || elements.length == 0) {
return Stream. empty();
}
return new Stream<>(new ObjArray<>(elements));
}
/**
* Creates a {@code Stream} from {@code Map} entries.
*
* @param the type of map keys
* @param the type of map values
* @param map the map with elements to be passed to stream
* @return the new stream
* @throws NullPointerException if {@code map} is null
*/
public static Stream of(Collection extends T> c) {
if (c == null || c.size() == 0) {
return Stream. empty();
}
return new Stream<>(c);
}
/**
* Creates a {@code Stream} from {@code Map} entries.
*
* @param the type of map keys
* @param the type of map values
* @param map the map with elements to be passed to stream
* @return the new stream
* @throws NullPointerException if {@code map} is null
*/
public static Stream> of(Map map) {
if (map == null || map.size() == 0) {
return Stream.> empty();
}
return new Stream<>(map.entrySet());
}
/**
* Creates a {@code Stream} from any class that implements {@code Iterator} interface.
*
* @param the type of the stream elements
* @param iterator the iterator with elements to be passed to stream
* @return the new stream
* @throws NullPointerException if {@code iterator} is null
*/
public static Stream of(Iterator extends T> iterator) {
Objects.requireNonNull(iterator);
return new Stream<>(iterator);
}
/**
* Creates a {@code Stream} by elements that generated by {@code Supplier}.
*
* @param the type of the stream elements
* @param supplier the {@code Supplier} of generated elements
* @return the new stream
* @throws NullPointerException if {@code supplier} is null
*/
public static Stream generate(final Supplier supplier) {
Objects.requireNonNull(supplier);
return new Stream<>(new ObjGenerate<>(supplier));
}
/**
* Creates a {@code Stream} by iterative application {@code UnaryOperator} function
* to an initial element {@code seed}. Produces {@code Stream} consisting of
* {@code seed}, {@code op(seed)}, {@code op(op(seed))}, etc.
*
* Example:
*
* seed: 1
* op: (a) -> a + 5
* result: [1, 6, 11, 16, ...]
*
*
* @param the type of the stream elements
* @param seed the initial value
* @param op operator to produce new element by previous one
* @return the new stream
* @throws NullPointerException if {@code op} is null
*/
public static Stream iterate(final T seed, final UnaryOperator op) {
Objects.requireNonNull(op);
return new Stream<>(new ObjIterate<>(seed, op));
}
/**
* Creates a {@code Stream} by iterative application {@code UnaryOperator} function
* to an initial element {@code seed}, conditioned on satisfying the supplied predicate.
*
* Example:
*
* seed: 0
* predicate: (a) -> a < 20
* op: (a) -> a + 5
* result: [0, 5, 10, 15]
*
*
* @param the type of the stream elements
* @param seed the initial value
* @param predicate a predicate to determine when the stream must terminate
* @param op operator to produce new element by previous one
* @return the new stream
* @throws NullPointerException if {@code op} is null
* @since 1.1.5
*/
public static Stream iterate(final T seed, final Predicate super T> predicate, final UnaryOperator op) {
Objects.requireNonNull(predicate);
return iterate(seed, op).takeWhile(predicate);
}
/**
* Concatenates two streams.
*
* Example:
*
* stream 1: [1, 2, 3, 4]
* stream 2: [5, 6]
* result: [1, 2, 3, 4, 5, 6]
*
*
* @param The type of stream elements
* @param stream1 the first stream
* @param stream2 the second stream
* @return the new concatenated stream
* @throws NullPointerException if {@code stream1} or {@code stream2} is null
*/
public static Stream concat(final Stream extends T> stream1, final Stream extends T> stream2) {
Objects.requireNonNull(stream1);
Objects.requireNonNull(stream2);
@SuppressWarnings("resource")
Stream result = new Stream<>(new ObjConcat<>(stream1.iterator, stream2.iterator));
return result.onClose(Compose.closeables(stream1, stream2));
}
/**
* Concatenates two iterators to a stream.
*
* Example:
*
* iterator 1: [1, 2, 3, 4]
* iterator 2: [5, 6]
* result: [1, 2, 3, 4, 5, 6]
*
*
* @param The type of iterator elements
* @param iterator1 the first iterator
* @param iterator2 the second iterator
* @return the new stream
* @throws NullPointerException if {@code iterator1} or {@code iterator2} is null
* @since 1.1.9
*/
public static Stream concat(final Iterator extends T> iterator1, final Iterator extends T> iterator2) {
Objects.requireNonNull(iterator1);
Objects.requireNonNull(iterator2);
return new Stream<>(new ObjConcat<>(iterator1, iterator2));
}
public static Stream concat(final Collection extends T> c1, final Collection extends T> c2) {
return concat(c1 == null ? Collections. emptyIterator() : c1.iterator(), c2 == null ? Collections. emptyIterator() : c2.iterator());
}
public static Stream concat(final T[] a, final T[] b) {
return concat(a == null ? Collections. emptyIterator() : Arrays.asList(a).iterator(),
b == null ? Collections. emptyIterator() : Arrays.asList(b).iterator());
}
/**
* Combines two streams by applying specified combiner function to each element at same position.
*
* Example:
*
* combiner: (a, b) -> a + b
* stream 1: [1, 2, 3, 4]
* stream 2: [5, 6, 7, 8]
* result: [6, 8, 10, 12]
*
*
* @param the type of first stream elements
* @param the type of second stream elements
* @param the type of elements in resulting stream
* @param stream1 the first stream
* @param stream2 the second stream
* @param combiner the combiner function used to apply to each element
* @return the new stream
* @throws NullPointerException if {@code stream1} or {@code stream2} is null
*/
public static Stream zip(final Stream extends F> stream1, final Stream extends S> stream2,
final BiFunction super F, ? super S, ? extends R> combiner) {
Objects.requireNonNull(stream1);
Objects.requireNonNull(stream2);
return Stream. zip(stream1.iterator, stream2.iterator, combiner);
}
/**
* Combines two iterators to a stream by applying specified combiner function to each element at same position.
*
* Example:
*
* combiner: (a, b) -> a + b
* stream 1: [1, 2, 3, 4]
* stream 2: [5, 6, 7, 8]
* result: [6, 8, 10, 12]
*
*
* @param the type of first iterator elements
* @param the type of second iterator elements
* @param the type of elements in resulting stream
* @param iterator1 the first iterator
* @param iterator2 the second iterator
* @param combiner the combiner function used to apply to each element
* @return the new stream
* @throws NullPointerException if {@code iterator1} or {@code iterator2} is null
* @since 1.1.2
*/
public static Stream zip(final Iterator extends F> iterator1, final Iterator extends S> iterator2,
final BiFunction super F, ? super S, ? extends R> combiner) {
Objects.requireNonNull(iterator1);
Objects.requireNonNull(iterator2);
return new Stream<>(new ObjZip<>(iterator1, iterator2, combiner));
}
public static Stream zip(final Collection extends F> c1, final Collection extends S> c2,
final BiFunction super F, ? super S, ? extends R> combiner) {
return zip(c1 == null ? Collections. emptyIterator() : c1.iterator(), c2 == null ? Collections. emptyIterator() : c2.iterator(), combiner);
}
public static Stream zip(final F[] a, final S[] b, final BiFunction super F, ? super S, ? extends R> combiner) {
return zip(a == null ? Collections. emptyIterator() : Arrays.asList(a).iterator(),
b == null ? Collections. emptyIterator() : Arrays.asList(b).iterator(), combiner);
}
/**
* Merges elements of two streams according to the supplied selector function.
*
* Example 1 — Merge two sorted streams:
*
* stream1: [1, 3, 8, 10]
* stream2: [2, 5, 6, 12]
* selector: (a, b) -> a < b ? TAKE_FIRST : TAKE_SECOND
* result: [1, 2, 3, 5, 6, 8, 10, 12]
*
*
* Example 2 — Concat two streams:
*
* stream1: [0, 3, 1]
* stream2: [2, 5, 6, 1]
* selector: (a, b) -> TAKE_SECOND
* result: [2, 5, 6, 1, 0, 3, 1]
*
*
* @param the type of the elements
* @param stream1 the first stream
* @param stream2 the second stream
* @param selector the selector function used to choose elements
* @return the new stream
* @throws NullPointerException if {@code stream1} or {@code stream2} is null
* @since 1.1.9
*/
public static Stream merge(final Stream extends T> stream1, final Stream extends T> stream2,
final BiFunction super T, ? super T, ObjMerge.MergeResult> selector) {
Objects.requireNonNull(stream1);
Objects.requireNonNull(stream2);
return Stream. merge(stream1.iterator, stream2.iterator, selector);
}
/**
* Merges elements of two iterators according to the supplied selector function.
*
* Example 1 — Merge two sorted iterators:
*
* iterator1: [1, 3, 8, 10]
* iterator2: [2, 5, 6, 12]
* selector: (a, b) -> a < b ? TAKE_FIRST : TAKE_SECOND
* result: [1, 2, 3, 5, 6, 8, 10, 12]
*
*
* Example 2 — Concat two iterators:
*
* iterator1: [0, 3, 1]
* iterator2: [2, 5, 6, 1]
* selector: (a, b) -> TAKE_SECOND
* result: [2, 5, 6, 1, 0, 3, 1]
*
*
* @param the type of the elements
* @param iterator1 the first iterator
* @param iterator2 the second iterator
* @param selector the selector function used to choose elements
* @return the new stream
* @throws NullPointerException if {@code iterator1} or {@code iterator2} is null
* @since 1.1.9
*/
public static Stream merge(final Iterator extends T> iterator1, final Iterator extends T> iterator2,
final BiFunction super T, ? super T, ObjMerge.MergeResult> selector) {
Objects.requireNonNull(iterator1);
Objects.requireNonNull(iterator2);
return new Stream<>(new ObjMerge<>(iterator1, iterator2, selector));
}
public static Stream merge(final Collection extends T> c1, final Collection extends T> c2,
final BiFunction super T, ? super T, ObjMerge.MergeResult> selector) {
return merge(c1 == null ? Collections. emptyIterator() : c1.iterator(), c2 == null ? Collections. emptyIterator() : c2.iterator(), selector);
}
public static Stream merge(final T[] a, final T[] b, final BiFunction super T, ? super T, ObjMerge.MergeResult> selector) {
return merge(a == null ? Collections. emptyIterator() : Arrays.asList(a).iterator(),
b == null ? Collections. emptyIterator() : Arrays.asList(b).iterator(), selector);
}
//
private final Iterator iterator;
private final Params params;
private Stream(Iterator extends T> iterator) {
this(null, iterator);
}
private Stream(Iterable extends T> iterable) {
this(null, new LazyIterator<>(iterable));
}
private Stream(Params params, Iterable extends T> iterable) {
this(params, new LazyIterator<>(iterable));
}
@SuppressWarnings("unchecked")
Stream(Params params, Iterator extends T> iterator) {
this.params = params;
this.iterator = (Iterator) iterator;
}
/**
* Returns internal stream iterator.
*
* @return internal stream iterator
*/
public Iterator iterator() {
return iterator;
}
/**
* Returns {@code Stream} with elements that satisfy the given predicate.
*
* This is an intermediate operation.
*
*
Example:
*
* predicate: (a) -> a > 2
* stream: [1, 2, 3, 4, -8, 0, 11]
* result: [3, 4, 11]
*
*
* @param predicate the predicate used to filter elements
* @return the new stream
*/
public Stream filter(final Predicate super T> predicate) {
return new Stream<>(params, new ObjFilter<>(iterator, predicate));
}
/**
* Returns {@code Stream} without null elements.
*
* This is an intermediate operation.
*
* @return the new stream
* @since 1.1.6
*/
public Stream skipNull() {
return filter(Fn.notNull());
}
/**
* Returns {@code Stream} with elements that obtained by applying the given function.
*
* This is an intermediate operation.
*
*
Example:
*
* mapper: (a) -> a + 5
* stream: [1, 2, 3, 4]
* result: [6, 7, 8, 9]
*
*
* @param the type of elements in resulting stream
* @param mapper the mapper function used to apply to each element
* @return the new stream
*/
public Stream map(final Function super T, ? extends R> mapper) {
return new Stream<>(params, new ObjMap<>(iterator, mapper));
}
/**
* Returns {@code IntStream} with elements that obtained by applying the given function.
*
* This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code IntStream}
* @see #map(com.annimon.stream.function.Function)
*/
public IntStream mapToInt(final ToIntFunction super T> mapper) {
return new IntStream(params, new ObjMapToInt<>(iterator, mapper));
}
/**
* Returns {@code LongStream} with elements that obtained by applying the given function.
*
*
This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code LongStream}
* @since 1.1.4
* @see #map(com.annimon.stream.function.Function)
*/
public LongStream mapToLong(final ToLongFunction super T> mapper) {
return new LongStream(params, new ObjMapToLong<>(iterator, mapper));
}
/**
* Returns {@code DoubleStream} with elements that obtained by applying the given function.
*
*
This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code DoubleStream}
* @since 1.1.4
* @see #map(com.annimon.stream.function.Function)
*/
public DoubleStream mapToDouble(final ToDoubleFunction super T> mapper) {
return new DoubleStream(params, new ObjMapToDouble<>(iterator, mapper));
}
@SuppressWarnings("unchecked")
public EntryStream mapToEntry(Function super T, Map.Entry> mapper) {
final Function, ?> identityMapper = Fn.identity();
if (mapper == identityMapper) {
return new EntryStream<>((Stream>) this);
} else {
return new EntryStream<>(this.map(mapper));
}
}
public EntryStream mapToEntry(final Function super T, K> keyMapper, final Function super T, V> valueMapper) {
return new EntryStream<>(this.map(new Function>() {
@Override
public Entry apply(T t) {
return new AbstractMap.SimpleImmutableEntry<>(keyMapper.apply(t), valueMapper.apply(t));
}
}));
}
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with the contents of a mapped stream produced by applying
* the provided mapping function to each element.
*
* This is an intermediate operation.
*
*
Example:
*
* mapper: (a) -> [a, a + 5]
* stream: [1, 2, 3, 4]
* result: [1, 6, 2, 7, 3, 8, 4, 9]
*
*
* @param the type of elements in resulting stream
* @param mapper the mapper function used to apply to each element
* @return the new stream
*/
public Stream flatMap(final Function super T, ? extends Stream extends R>> mapper) {
return new Stream<>(params, new ObjFlatMap<>(iterator, mapper));
}
public Stream flatCollection(final Function super T, ? extends Collection extends R>> mapper) {
return flatMap(new Function>() {
@Override
public Stream apply(T t) {
final Collection extends R> c = mapper.apply(t);
return c == null || c.size() == 0 ? Stream. empty() : Stream.of(c);
}
});
}
public Stream flatArray(final Function super T, ? extends R[]> mapper) {
return flatMap(new Function>() {
@Override
public Stream apply(T t) {
final R[] a = mapper.apply(t);
return a == null || a.length == 0 ? Stream. empty() : Stream.of(a);
}
});
}
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with the contents of a mapped stream produced by applying
* the provided mapping function to each element.
*
* This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code IntStream}
* @see #flatMap(com.annimon.stream.function.Function)
*/
public IntStream flatMapToInt(final Function super T, ? extends IntStream> mapper) {
return new IntStream(params, new ObjFlatMapToInt<>(iterator, mapper));
}
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with the contents of a mapped stream produced by applying
* the provided mapping function to each element.
*
*
This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code LongStream}
* @see #flatMap(com.annimon.stream.function.Function)
*/
public LongStream flatMapToLong(final Function super T, ? extends LongStream> mapper) {
return new LongStream(params, new ObjFlatMapToLong<>(iterator, mapper));
}
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with the contents of a mapped stream produced by applying
* the provided mapping function to each element.
*
*
This is an intermediate operation.
*
* @param mapper the mapper function used to apply to each element
* @return the new {@code DoubleStream}
* @see #flatMap(com.annimon.stream.function.Function)
*/
public DoubleStream flatMapToDouble(final Function super T, ? extends DoubleStream> mapper) {
return new DoubleStream(params, new ObjFlatMapToDouble<>(iterator, mapper));
}
public EntryStream flatMapToEntry(Function super T, ? extends Stream extends Map.Entry>> mapper) {
return EntryStream.of(flatMap(mapper));
}
// public EntryStream flatMapToEntry2(final Function super T, ? extends Map> mapper) {
// final Function>> mapper2 = new Function>>() {
// @Override
// public Stream> apply(T t) {
// return Stream.of(mapper.apply(t));
// }
// };
//
// return EntryStream.of(flatMap(mapper2));
// }
/**
* Returns {@code Stream} with indexed elements.
* Indexing starts from 0 with step 1.
*
* This is an intermediate operation.
*
*
Example:
*
* stream: ["a", "b", "c"]
* result: [(0, "a"), (1, "b"), (2, "c")]
*
*
* @return the new {@code IntPair} stream
* @since 1.1.2
*/
public Stream> indexed() {
return map(new Function>() {
private int index = 0;
@Override
public Indexed apply(T t) {
return new Indexed<>(index++, t);
}
});
}
/**
* Returns {@code Stream} with distinct elements (as determined by {@code hashCode} and {@code equals} methods).
*
* This is a stateful intermediate operation.
*
*
Example:
*
* stream: [1, 4, 2, 3, 3, 4, 1]
* result: [1, 4, 2, 3]
*
*
* @return the new stream
*/
public Stream distinct() {
return new Stream<>(params, new ObjDistinct<>(iterator));
}
/**
* Returns {@code Stream} with distinct elements (as determined by {@code hashCode}
* and {@code equals} methods) according to the given classifier function.
*
* This is a stateful intermediate operation.
*
*
Example:
*
* classifier: (str) -> str.length()
* stream: ["a", "bc", "d", "ef", "ghij"]
* result: ["a", "bc", "ghij"]
*
*
* @param keyExtractor the classifier function
* @return the new stream
* @since 1.1.8
*/
public Stream distinctBy(Function super T, ? extends K> keyExtractor) {
return new Stream<>(params, new ObjDistinctBy<>(iterator, keyExtractor));
}
/**
* Returns {@code Stream} with sorted elements (as determinated by {@link Comparable} interface).
*
* This is a stateful intermediate operation.
*
If the elements of this stream are not {@link Comparable},
* a {@code java.lang.ClassCastException} may be thrown when the terminal operation is executed.
*
*
Example:
*
* stream: [3, 4, 1, 2]
* result: [1, 2, 3, 4]
*
*
* @return the new stream
*/
public Stream sorted() {
return sorted(new Comparator() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public int compare(T o1, T o2) {
Comparable c1 = (Comparable) o1;
Comparable c2 = (Comparable) o2;
return c1.compareTo(c2);
}
});
}
/**
* Returns {@code Stream} with sorted elements (as determinated by provided {@code Comparator}).
*
* This is a stateful intermediate operation.
*
*
Example:
*
* comparator: (a, b) -> -a.compareTo(b)
* stream: [1, 2, 3, 4]
* result: [4, 3, 2, 1]
*
*
* @param comparator the {@code Comparator} to compare elements
* @return the new stream
*/
public Stream sorted(final Comparator super T> comparator) {
return new Stream<>(params, new ObjSorted<>(iterator, comparator));
}
/**
* Returns {@code Stream} with sorted elements (as determinated by {@code Comparable} interface).
* Each element transformed by given function {@code f} before comparing.
*
* This is a stateful intermediate operation.
*
*
Example:
*
* f: (a) -> -a
* stream: [1, 2, 3, 4]
* result: [4, 3, 2, 1]
*
*
* @param the type of the result of transforming function
* @param keyExtractor the transformation function
* @return the new stream
*/
public > Stream sortedBy(final Function super T, U> keyExtractor) {
return sorted(Comparators.comparingBy(keyExtractor));
}
/**
* Partitions {@code Stream} into {@code Map} entries according to the given classifier function.
*
* This is a stateful intermediate operation.
*
*
Example:
*
* classifier: (str) -> str.length()
* stream: ["a", "bc", "d", "ef", "ghij"]
* result: [{1: ["a", "d"]}, {2: ["bc", "ef"]}, {4: ["ghij"]}]
*
*
* @param the type of the keys, which are result of the classifier function
* @param classifier the classifier function
* @return the new stream
*/
public Stream>> groupBy(final Function super T, ? extends K> classifier) {
final Map> map = collect(Collectors. groupingBy(classifier));
return new Stream<>(params, map.entrySet());
}
public Stream> groupBy(Function super T, ? extends K> classifier, Collector super T, A, D> downstream) {
final Map map = collect(Collectors.groupingBy(classifier, downstream));
return new Stream<>(params, map.entrySet());
}
public Stream> groupBy(final Function super T, ? extends K> classifier, final Collector super T, A, D> downstream,
final Supplier