0% found this document useful (0 votes)
6 views18 pages

Java Stream API Collectors Exhaustive

This document serves as an exhaustive reference for the Java Stream API and Collectors, detailing each method's purpose, examples, and corner cases. It covers concepts such as stream creation, intermediate and terminal operations, primitive streams, optionals, parallel streams, and a complete catalog of collectors. The document is structured for practical use in interviews and real-world coding scenarios.

Uploaded by

techbuzz3934
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views18 pages

Java Stream API Collectors Exhaustive

This document serves as an exhaustive reference for the Java Stream API and Collectors, detailing each method's purpose, examples, and corner cases. It covers concepts such as stream creation, intermediate and terminal operations, primitive streams, optionals, parallel streams, and a complete catalog of collectors. The document is structured for practical use in interviews and real-world coding scenarios.

Uploaded by

techbuzz3934
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 18

Java Stream API & Collectors —

Exhaustive Reference with Examples


Everything you need for interviews and real-world coding: each Stream/Collectors method with
purpose, example, and corner cases. Common/most useful APIs are highlighted in green.

Quick Contents
• 1. Streams: Concepts & Building Blocks

• 2. Stream Sources (creation)

• 3. Intermediate Operations

• 4. Terminal Operations

• 5. Primitive Streams (IntStream, LongStream, DoubleStream)

• 6. Optional & OptionalX from streams

• 7. Parallel Streams & Spliterators (internals)

• 8. Collectors — complete catalog

• 9. Corner Cases & Gotchas

• 10. Practical Patterns & Recipes

1) Streams: Concepts & Building Blocks


• Pipelines: Source → Intermediate ops (zero or more) → Terminal op (one).

• Laziness: intermediate ops do not run until a terminal op is invoked.

• Non-mutating by design: operate on views, not in-place mutation.

• Base interfaces: BaseStream<T,S>, Stream<T>, IntStream, LongStream, DoubleStream.

// A small pipeline
List<String> out = names.stream() // Source
.filter(n -> n.length() > 3) // Intermediate
(lazy)
.map(String::toUpperCase)
.sorted()
.toList(); // Terminal
(executes)
Most useful daily combo: filter → map → collect(toList()).

2) Stream Sources (Creation)


Stream.empty()
Purpose: Empty stream.

Stream<String> s = Stream.<String>empty();

Corner case / tip: Useful for composing APIs without nulls.

Stream.of(T...)
Purpose: Varargs of elements.

Stream<Integer> s = Stream.of(1,2,3);

Corner case / tip: Beware of Stream.of(array) vs Arrays.stream(array).

Stream.ofNullable(T)
Purpose: 0 or 1 element depending on null.

Stream<String> s = Stream.ofNullable(System.getenv("USER"));

Corner case / tip: Great for null-safe pipelines (JDK 9+).

Stream.generate(Supplier)
Purpose: Infinite stream; use limit.

Stream<Double> r = Stream.generate(Math::random).limit(3);

Corner case / tip: Make sure to limit to avoid infinite processing.

Stream.iterate(seed, f)
Purpose: Infinite sequence.

Stream<Integer> evens = Stream.iterate(0, x->x+2).limit(5);

Corner case / tip: Use iterate(seed, hasNext, next) to bound.

Stream.iterate(seed, hasNext, next)


Purpose: Finite, bounded iteration.

Stream<Integer> s = Stream.iterate(0, x->x<10, x->x+3);

Corner case / tip: Stops when predicate fails (JDK 9+).

Collection.stream()
Purpose: From collections.

list.stream();
Corner case / tip: ArrayList streams split well in parallel.

Collection.parallelStream()
Purpose: Parallel stream from collection.

list.parallelStream();

Corner case / tip: Use with care; see parallel section.

Arrays.stream(array)
Purpose: Array to stream.

int sum = Arrays.stream(new int[]{1,2,3}).sum();

Corner case / tip: Prefer primitive specializations to avoid boxing.

Files.lines(Path)
Purpose: Lines of a file (lazy, must close).

try (Stream<String> lines = Files.lines(path)) { lines.forEach(...);}

Corner case / tip: Use try-with-resources to close.

BufferedReader.lines()
Purpose: Lines from reader.

try (var br = Files.newBufferedReader(path))


{ br.lines().forEach(...);}

Corner case / tip: Closes with reader.

Pattern.splitAsStream(CharSequence)
Purpose: Split string into stream.

Pattern.compile(",").splitAsStream("a,b,c").forEach(...);

Corner case / tip: Good for large text parsing.

Stream.builder()
Purpose: Manually build stream.

Stream<String> s =
Stream.<String>builder().add("a").add("b").build();

Corner case / tip: Single-use builder.

3) Intermediate Operations
filter(Predicate)
Purpose: Keep elements that satisfy predicate.
List<String> longOnes = words.stream().filter(w -> w.length() >=
5).toList();

Corner case / tip: Stateless, short-circuits per element only with terminal match ops.

Common

map(Function)
Purpose: Transform each element.

List<Integer> lens = words.stream().map(String::length).toList();

Corner case / tip: Avoid heavy allocations; consider mapToInt for primitives.

Common

mapToInt/mapToLong/mapToDouble
Purpose: Project to primitive streams.

int sum = words.stream().mapToInt(String::length).sum();

Corner case / tip: Eliminates boxing; enables numeric ops.

flatMap(Function<T,Stream<R>>)
Purpose: Flatten one level of streams.

List<String> tags = posts.stream().flatMap(p ->


p.tags().stream()).toList();

Corner case / tip: Do not return null; return Stream.empty() for no elements.

Common

flatMapToInt/Long/Double
Purpose: Flatten to primitive streams.

int total = paragraphs.stream().flatMapToInt(s -> s.chars()).sum();

Corner case / tip: Good for char/byte processing.

mapMulti(BiConsumer<T,Consumer<R>>) (JDK 16+)


Purpose: Emit 0..N results without creating intermediate streams.

Stream.<List<Integer>>of(List.of(1,2), List.of(3))
.mapMulti((lst, sink) -> lst.forEach(sink))
.forEach(System.out::println);

Corner case / tip: Faster than flatMap when you can push directly to sink.

distinct()
Purpose: Remove duplicates using equals/hashCode.
List<Integer> uniq = nums.stream().distinct().toList();

Corner case / tip: Mutable elements can break equals/hashCode; avoid mutating after distinct.

Common

sorted()
Purpose: Natural order sort.

List<String> s = words.stream().sorted().toList();

Corner case / tip: Requires Comparable or use Comparator overload.

Common

sorted(Comparator)
Purpose: Custom comparator.

var s =
words.stream().sorted(Comparator.comparingInt(String::length)).toList
();

Corner case / tip: Avoid inconsistent comparators (must be transitive).

Common

peek(Consumer)
Purpose: Debug/observe elements.

List<String> out = words.stream().peek(System.out::println).toList();

Corner case / tip: Does nothing without terminal op; avoid side effects in production.

limit(long)
Purpose: Take first n elements.

var first10 = stream.limit(10).toList();

Corner case / tip: On unordered parallel streams, may pull any n elements.

Common

skip(long)
Purpose: Skip first n elements.

var tail = stream.skip(5).toList();

Corner case / tip: Combined with limit for pagination.

Common
takeWhile(Predicate) (JDK 9+)
Purpose: Take prefix until predicate fails.

Stream.of(1,2,2,3,1).takeWhile(x->x<=2).toList(); // [1,2,2]

Corner case / tip: Works on encounter order; not a global filter.

Common

dropWhile(Predicate) (JDK 9+)


Purpose: Drop prefix while predicate holds.

Stream.of(1,1,2,3).dropWhile(x->x==1).toList(); // [2,3]

Corner case / tip: Complements takeWhile.

Common

boxed()
Purpose: Convert primitive stream to boxed Stream<T>.

Stream<Integer> s = IntStream.range(0,3).boxed();

Corner case / tip: Avoid if not needed; introduces boxing.

sequential()/parallel()
Purpose: Switch execution mode.

list.stream().parallel().map(...).sequential().forEach(...);

Corner case / tip: Parallel can hurt if tasks are small/I-O bound.

unordered()
Purpose: Drop ordering constraints (hint).

long count = list.stream().unordered().distinct().count();

Corner case / tip: Enables optimizations for some sources.

onClose(Runnable)
Purpose: Register close handler.

try (var s = Files.lines(path).onClose(() ->


System.out.println("closed"))) { s.count(); }

Corner case / tip: Runs when stream is closed; multiple onClose accumulate.
4) Terminal Operations
forEach(Consumer)
Purpose: Consume elements in arbitrary order (unless ordered source).

words.stream().forEach(System.out::println);

Corner case / tip: In parallel, order not guaranteed; use forEachOrdered for ordering.

forEachOrdered(Consumer)
Purpose: Consume in encounter order.

words.parallelStream().forEachOrdered(System.out::println);

Corner case / tip: May reduce parallel performance vs forEach.

toArray()
Purpose: Materialize elements to Object[].

Object[] arr = stream.toArray();

Corner case / tip: Prefer typed array with generator.

toArray(IntFunction<A[]>)
Purpose: Materialize to typed array.

String[] arr = words.stream().toArray(String[]::new);

Corner case / tip: Avoid casting from Object[].

reduce(identity, accumulator)
Purpose: Fold with identity and accumulator.

int sum = nums.stream().reduce(0, Integer::sum);

Corner case / tip: Accumulator must be associative; identity should be neutral element.

Common

reduce(accumulator)
Purpose: Fold without identity → Optional<T>.

Optional<Integer> sum = nums.stream().reduce(Integer::sum);

Corner case / tip: Empty stream → Optional.empty().

Common

reduce(identity, accumulator, combiner)


Purpose: Parallel-friendly fold.
int sum = nums.parallelStream().reduce(0, Integer::sum,
Integer::sum);

Corner case / tip: Combiner merges partials; must be associative.

Common

collect(Collector)
Purpose: General reduction using Collector.

Map<String, Long> freq =


words.stream().collect(Collectors.groupingBy(w->w,
Collectors.counting()));

Corner case / tip: Prefer collectors for complex aggregations.

Common

collect(supplier,accumulator,combiner)
Purpose: Custom mutable reduction.

StringBuilder sb = words.stream().collect(StringBuilder::new, (b,w)-


>b.append(w), StringBuilder::append);

Corner case / tip: Ensure thread-safe combiner for parallel.

Common

min(Comparator)
Purpose: Minimum by comparator.

Optional<String> s =
words.stream().min(Comparator.comparingInt(String::length));

Corner case / tip: Empty stream → Optional.empty().

max(Comparator)
Purpose: Maximum by comparator.

Optional<String> s =
words.stream().max(Comparator.comparingInt(String::length));

Corner case / tip: Same Optional behavior as min.

count()
Purpose: Element count.

long c = stream.count();

Corner case / tip: Consumes the stream; cannot reuse.


anyMatch/allMatch/noneMatch(Predicate)
Purpose: Short-circuiting match operations.

boolean ok = nums.stream().anyMatch(n -> n > 100);

Corner case / tip: Respect encounter order; stop early when possible.

Common

findFirst()
Purpose: First element in encounter order.

Optional<String> first = stream.findFirst();

Corner case / tip: On unordered sources, still deterministic for sequential streams.

Common

findAny()
Purpose: Any element (may be faster on parallel).

Optional<String> any = stream.parallel().findAny();

Corner case / tip: For sequential ordered streams, behaves like findFirst.

toList() (JDK 16+)


Purpose: Collect into unmodifiable list.

List<String> list = stream.toList();

Corner case / tip: Adding to result throws UnsupportedOperationException.

Common

5) Primitive Streams (IntStream, LongStream, DoubleStream)


Why use them? Eliminate boxing and unlock numeric helpers.

range(a,b) / rangeClosed(a,b)
Purpose: Generate sequences.

IntStream r = IntStream.range(0, 5); // 0..4

Corner case / tip: Prefer rangeClosed when inclusive upper bound is desired.

sum() / average() / min() / max()


Purpose: Numeric reducers.

double avg = IntStream.of(1,2,3).average().orElse(0.0);

Corner case / tip: average returns OptionalDouble (watch empties).


summaryStatistics()
Purpose: Count, sum, min, max, avg in one object.

IntSummaryStatistics s = IntStream.of(1,2,3).summaryStatistics();

Corner case / tip: Great for reporting.

map/mapToObj/flatMap
Purpose: Transformations.

Stream<Character> chars = "abc".chars().mapToObj(c -> (char)c);

Corner case / tip: chars() yields an IntStream of code points.

boxed()
Purpose: Convert to Stream<Integer/Long/Double>.

Stream<Integer> s = IntStream.range(0,3).boxed();

Corner case / tip: Only box when necessary.

asLongStream()/asDoubleStream()
Purpose: Widening conversions.

DoubleStream ds = IntStream.of(1,2).asDoubleStream();

Corner case / tip: Precision follows double rules.

Common: range/rangeClosed, sum/average, summaryStatistics.

6) Optionals from Streams


• Generated by: findFirst/findAny/min/max/reduce(without identity)/average (primitive).

Optional<String> first = words.stream().filter(w->!


w.isBlank()).findFirst();
String value = first.orElse("N/A");

• APIs: orElse, orElseGet, orElseThrow, ifPresent, ifPresentOrElse, map, flatMap, filter.

• Primitive variants: OptionalInt/Long/Double.

7) Parallel Streams & Spliterators (Internals)


• Good for CPU-bound tasks over large datasets where operations are associative and
independent.

• Avoid with I/O-bound tasks or shared mutable state.


Map<String, Long> freq = words.parallelStream()
.collect(Collectors.groupingByConcurrent(w -> w,
Collectors.counting()));

• Spliterator characteristics: ORDERED, DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE,


CONCURRENT, SUBSIZED.

• Sources that split well: ArrayList, arrays; Poor: LinkedList, I/O-backed.

8) Collectors — Complete Catalog


toList()
Purpose: Collect to a modifiable list (implementation-specific). Prefer Stream.toList() for
unmodifiable.

List<String> list = stream.collect(Collectors.toList());

Corner case / tip: May not be unmodifiable; use Stream.toList() (JDK 16+) or
toUnmodifiableList().

Common

toUnmodifiableList()
Purpose: Unmodifiable list.

List<String> list = stream.collect(Collectors.toUnmodifiableList());

Corner case / tip: Adding throws UnsupportedOperationException.

toSet()
Purpose: Collect to a modifiable set.

Set<String> set = stream.collect(Collectors.toSet());

Corner case / tip: Order unspecified; may lose duplicates.

toUnmodifiableSet()
Purpose: Unmodifiable set.

Set<String> set = stream.collect(Collectors.toUnmodifiableSet());

Corner case / tip: Unmodifiable result.

toCollection(Supplier<C>)
Purpose: Specify concrete collection type.

LinkedHashSet<String> lhs =
stream.collect(Collectors.toCollection(LinkedHashSet::new));

Corner case / tip: Use for predictable order or special collections.


joining()
Purpose: Concatenate CharSequence elements with no delimiter.

String s = Stream.of("a","b").collect(Collectors.joining());

Corner case / tip: Elements must be CharSequence.

Common

joining(delim)
Purpose: Concatenate with delimiter.

String s = Stream.of("a","b").collect(Collectors.joining(", "));

Corner case / tip: Common for CSV-like outputs.

Common

joining(delim,prefix,suffix)
Purpose: Concatenate with adornments.

String s = Stream.of("a","b").collect(Collectors.joining(",
","[","]"));

Corner case / tip: Great for pretty printing.

Common

toMap(keyMapper, valueMapper)
Purpose: Collect to Map, error on duplicate key.

Map<String,Integer> m =
items.stream().collect(Collectors.toMap(Item::name, Item::qty));

Corner case / tip: Duplicate keys cause IllegalStateException.

Common

toMap(k, v, merge)
Purpose: Provide merge function for duplicates.

Map<String,Integer> m = items.stream().collect(Collectors.toMap(
Item::name, Item::qty, Integer::max));

Corner case / tip: Common for keeping max/min/last value.

Common

toMap(k, v, merge, mapSupplier)


Purpose: Also choose map type.
TreeMap<String,Integer> m = items.stream().collect(Collectors.toMap(
Item::name, Item::qty, Integer::max, TreeMap::new));

Corner case / tip: Pick LinkedHashMap to preserve order.

Common

toUnmodifiableMap(k, v)
Purpose: Unmodifiable map, error on duplicate key.

Map<String,Integer> m =
items.stream().collect(Collectors.toUnmodifiableMap(Item::name,
Item::qty));

Corner case / tip: Duplicate keys throw IllegalStateException.

toUnmodifiableMap(k, v, merge)
Purpose: Unmodifiable map with merge.

Map<String,Integer> m =
items.stream().collect(Collectors.toUnmodifiableMap(
Item::name, Item::qty, Integer::max));

Corner case / tip: Merge resolves duplicates before freezing.

toConcurrentMap(k, v)
Purpose: Concurrent map, error on duplicate key.

ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(Item::name,
Item::qty));

Corner case / tip: Use for parallel pipelines.

toConcurrentMap(k, v, merge)
Purpose: Concurrent map with merge.

ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(
Item::name, Item::qty, Integer::sum));

Corner case / tip: Merge function is required for duplicates.

toConcurrentMap(k, v, merge, mapSupplier)


Purpose: Concurrent map with custom type.

ConcurrentMap<String,Integer> m =
items.parallelStream().collect(Collectors.toConcurrentMap(
Item::name, Item::qty, Integer::sum, ConcurrentHashMap::new));

Corner case / tip: Provide ConcurrentHashMap or subclass.


groupingBy(classifier)
Purpose: Map<K, List<T>> grouping.

Map<String,List<Employee>> byDept =
emps.stream().collect(Collectors.groupingBy(Emp::dept));

Corner case / tip: Default Map is HashMap; values are Lists.

Common

groupingBy(classifier, downstream)
Purpose: Customize value reduction.

Map<String,Long> countByDept =
emps.stream().collect(Collectors.groupingBy(Emp::dept,
Collectors.counting()));

Corner case / tip: Downstream can be any collector.

Common

groupingBy(classifier, mapFactory, downstream)


Purpose: Choose map type and downstream.

TreeMap<String,Double> avgSalary =
emps.stream().collect(Collectors.groupingBy(
Emp::dept, TreeMap::new,
Collectors.averagingDouble(Emp::salary)));

Corner case / tip: Pick LinkedHashMap for stable ordering.

Common

groupingByConcurrent(...)
Purpose: Concurrent variant for parallel pipelines.

ConcurrentMap<String,Long> c =
emps.parallelStream().collect(Collectors.groupingByConcurrent(
Emp::dept, Collectors.counting()));

Corner case / tip: Use with parallel streams for speed.

Common

partitioningBy(predicate)
Purpose: Split into true/false buckets.

Map<Boolean,List<Employee>> part =
emps.stream().collect(Collectors.partitioningBy(Emp::isActive));

Corner case / tip: Always two keys: true/false.


Common

partitioningBy(predicate, downstream)
Purpose: Partition with value reduction.

Map<Boolean,Long> counts =
emps.stream().collect(Collectors.partitioningBy(Emp::isActive,
Collectors.counting()));

Corner case / tip: Useful when predicate is binary.

Common

counting()
Purpose: Number of elements.

long c = stream.collect(Collectors.counting());

Corner case / tip: Equivalent to count().

Common

summingInt/Long/Double(mapper)
Purpose: Sum over mapped values.

int total =
people.stream().collect(Collectors.summingInt(Person::age));

Corner case / tip: Be aware of overflow for int/long sums.

Common

averagingInt/Long/Double(mapper)
Purpose: Average over mapped values.

double avg =
people.stream().collect(Collectors.averagingDouble(Person::salary));

Corner case / tip: Averages are double/Double.

Common

summarizingInt/Long/Double(mapper)
Purpose: Summary stats object.

DoubleSummaryStatistics s =
people.stream().collect(Collectors.summarizingDouble(Person::salary))
;

Corner case / tip: Includes count, sum, min, avg, max.


mapping(mapper, downstream)
Purpose: Map then collect in one pass.

Set<String> names =
people.stream().collect(Collectors.mapping(Person::name,
Collectors.toSet()));

Corner case / tip: Avoids separate map + collect.

filtering(predicate, downstream)
Purpose: Filter then collect in one pass (JDK 9+).

Map<String,List<Employee>> onlyActive =
emps.stream().collect(Collectors.groupingBy(
Emp::dept, Collectors.filtering(Emp::isActive,
Collectors.toList())));

Corner case / tip: Great inside groupingBy.

flatMapping(mapperToStream, downstream)
Purpose: flatMap then collect (JDK 9+).

Map<String,Set<String>> tagSets =
posts.stream().collect(Collectors.groupingBy(
Post::author, Collectors.flatMapping(p -> p.tags().stream(),
Collectors.toSet())));

Corner case / tip: Avoids extra intermediate streams.

reducing(identity, mapper, op)


Purpose: Reduction as collector.

int maxLen = words.stream().collect(Collectors.reducing(0,


String::length, Integer::max));

Corner case / tip: Prefer summarizing/averaging when available.

collectingAndThen(downstream, finisher)
Purpose: Post-process collector result.

List<String> unmod =
words.stream().collect(Collectors.collectingAndThen(
Collectors.toList(), List::copyOf));

Corner case / tip: Use for wrapping (e.g., unmodifiable).

Common

teeing(down1, down2, merger) (JDK 12+)


Purpose: Run two collectors at once then merge.
record MinMax(int min, int max) {}
MinMax mm = nums.stream().collect(Collectors.teeing(
Collectors.minBy(Integer::compareTo),
Collectors.maxBy(Integer::compareTo),
(min,max) -> new MinMax(min.orElseThrow(), max.orElseThrow())
));

Corner case / tip: Great for min+max in one pass.

Common

9) Corner Cases & Gotchas


• Empty min/max/average → Optional.empty / OptionalDouble.empty. Handle with
orElse/elseThrow.

• Collectors.toMap without merge → IllegalStateException on duplicate key.

• Stream.peek does nothing without terminal op; avoid side effects.

• Stream.toList() returns unmodifiable list (JDK 16+).

• Files.lines must be closed; use try-with-resources.

• distinct relies on equals/hashCode; do not mutate keys after insertion.

• Parallel forEach is unordered; use forEachOrdered if you need order (slower).

• groupingBy default map is HashMap; use LinkedHashMap/TreeMap if you need order/sort.

10) Practical Patterns & Recipes


Frequency Map
Map<String, Long> freq =
words.stream()
.map(String::toLowerCase)
.collect(Collectors.groupingBy(w -> w,
Collectors.counting()));

Top N per Group


Map<String, List<Employee>> top3 =
emps.stream().collect(Collectors.groupingBy(
Emp::dept,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparing(Employee::score)
.reversed())
.limit(3)
.toList()
)
));

Safe toMap with Merge


Map<String, Integer> best =
entries.stream().collect(Collectors.toMap(
e -> e.key(), e -> e.value(), Integer::max
));

First Non-Empty String


Optional<String> first =
strings.stream().filter(s -> s != null && !
s.isBlank()).findFirst();

You might also like