Working with Stream API
1. Creating Streams
| Source | Factory | Notes |
|---|---|---|
| Collection | coll.stream() / parallelStream() | Most common |
| Array | Arrays.stream(arr) | Primitive overloads |
| Values | Stream.of(a, b, c) | Varargs |
| Range | IntStream.range(0, n) | Half-open |
| Generate | Stream.generate(supplier) | Infinite |
| Iterate | Stream.iterate(seed, fn) / iterate(seed, hasNext, next) | Bounded form Java 9+ |
| Files | Files.lines(path) | Auto-closed |
| Builder | Stream.builder().add(x).build() | Imperative add |
| Empty | Stream.empty() | Avoid null |
2. Using Intermediate Operations
| Operation | Stateless? | Purpose |
|---|---|---|
filter(Predicate) | Yes | Keep matching |
map(Function) | Yes | 1:1 transform |
flatMap(Function) | Yes | 1:N flatten |
mapMulti Java 16+ | Yes | Imperative flatten |
distinct() | Stateful | Dedup via equals |
sorted() | Stateful | Buffers all elements |
peek(Consumer) | Yes | Debug only |
limit(n) / skip(n) | Stateful | Slicing |
takeWhile / dropWhile | Stateful | Java 9+ |
3. Using Terminal Operations
| Operation | Returns | Short-circuits |
|---|---|---|
forEach / forEachOrdered | void | No |
collect(Collector) | R | No |
toList() Java 16+ | Unmodifiable List | No |
reduce | T or Optional<T> | No |
count | long | No (sized → yes) |
min / max | Optional | No |
findFirst / findAny | Optional | Yes |
anyMatch / allMatch / noneMatch | boolean | Yes |
toArray | Object[] / T[] | No |
4. Understanding Stream Pipeline
Source → [Intermediate ops fused] → Terminal op
(lazy, no work) (triggers traversal)
Per element: filter → map → flatMap → ... → terminal
(pull model — terminal pulls one element through the pipeline)
| Property | Meaning |
|---|---|
| Lazy | Intermediate ops do nothing until terminal |
| Single use | Stream cannot be reused after terminal |
| Fusion | Adjacent stateless ops combined |
| Encounter order | Preserved unless unordered() |
5. Working with Primitive Streams
| Stream | Box | Specialized Ops |
|---|---|---|
IntStream | Integer | sum, average, summaryStatistics, range |
LongStream | Long | Same as IntStream |
DoubleStream | Double | Same minus range |
Example: Avoid boxing
int total = orders.stream().mapToInt(Order::amount).sum();
IntSummaryStatistics st = orders.stream().mapToInt(Order::amount).summaryStatistics();
6. Using Collectors
| Collector | Result |
|---|---|
toList() / toSet() | List / Set |
toUnmodifiableList() | Immutable list |
toMap(k, v[, merge]) | Map |
groupingBy(classifier[, downstream]) | Map<K, List<V>> |
partitioningBy(predicate) | Map<Boolean, List> |
counting() | Long count |
summingInt / averagingInt | Aggregation |
joining(delim, prefix, suffix) | String |
mapping / filtering / flatMapping | Downstream adapters |
teeing(c1, c2, merger) Java 12+ | Combine two collectors |
Example: Group + summarize
Map<String, Long> byDept = emps.stream()
.collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
Map<String, Double> avgSalary = emps.stream()
.collect(Collectors.groupingBy(Employee::dept, Collectors.averagingDouble(Employee::salary)));
7. Creating Custom Collectors (Collector.of())
| Argument | Type | Role |
|---|---|---|
| supplier | Supplier<A> | New mutable container |
| accumulator | BiConsumer<A,T> | Add element |
| combiner | BinaryOperator<A> | Merge for parallel |
| finisher | Function<A,R> | Optional final transform |
| characteristics | CONCURRENT, UNORDERED, IDENTITY_FINISH | Hints |
Example: Top-N collector
public static <T> Collector<T, ?, List<T>> topN(int n, Comparator<? super T> cmp) {
return Collector.of(
() -> new PriorityQueue<T>(cmp),
(q, x) -> { q.add(x); if (q.size() > n) q.poll(); },
(a, b) -> { b.forEach(a::add); while (a.size() > n) a.poll(); return a; },
q -> q.stream().sorted(cmp.reversed()).toList()
);
}
8. Using Parallel Streams (parallelStream())
| Aspect | Detail |
|---|---|
| Pool | Common ForkJoinPool (size = cores − 1) |
| Custom pool | Submit stream task to ForkJoinPool |
| Good fit | Large N, CPU-bound, splittable source |
| Bad fit | Small N, blocking I/O, ordered ops |
| Reduce | Identity must satisfy combiner(identity, x) == x |
Warning: Avoid shared mutable state and side effects in parallel streams; collisions cause non-determinism.
9. Understanding Stream Performance
| Factor | Impact |
|---|---|
| Auto-boxing | Use primitive streams |
| Stateful ops | Buffer entire stream (sorted, distinct) |
| Splitability | ArrayList good; LinkedList poor |
| Short-circuit | Place selective filters early |
| Object overhead | vs plain for-loop ~ 2-5× |
10. Handling Infinite Streams
| Pattern | Example |
|---|---|
| Bounded by limit | Stream.iterate(0, i->i+1).limit(100) |
| Bounded form | Stream.iterate(0, i->i<100, i->i+1) |
| takeWhile | stream.takeWhile(x -> x < 50) |
| findFirst on infinite | OK if predicate eventually matches |
Warning: Never call
count(), collect(), or sorted() on an unbounded stream.11. Using FlatMap for Nested Structures
| Variant | Maps Each Element To |
|---|---|
flatMap | Stream<R> |
flatMapToInt/Long/Double | Primitive stream |
mapMulti | Pushes 0..N to consumer |
Example: Flatten orders
List<Item> allItems = orders.stream()
.flatMap(o -> o.items().stream())
.toList();
12. Debugging Streams (peek())
| Tool | Use |
|---|---|
peek | Inspect elements between ops |
| Logger.atDebug | Conditional logging |
| IntelliJ Stream Trace | Visualize per-stage values |
| Stream Debugger plugin | Step through pipeline |
Note: JIT may elide
peek if its result is unused after a non-stateful terminal — don't rely on it for production side effects.13. Using takeWhile() and dropWhile() (Java 9+)
| Method | Behavior | Ordered Stream |
|---|---|---|
takeWhile(p) | Take prefix while p holds | Stops at first false |
dropWhile(p) | Skip prefix while p holds | Emits rest |
vs filter | Position-aware vs all elements | Different semantics |
14. Using Stream.toList() (Java 16+)
| Aspect | toList() | collect(toList()) |
|---|---|---|
| Mutability | Unmodifiable | Mutable ArrayList |
| Nulls | Allowed | Allowed |
| Performance | Slightly faster (presized) | Baseline |
| Recommendation | Default choice | Use only when mutability needed |