Working with Stream API

1. Creating Streams

SourceFactoryNotes
Collectioncoll.stream() / parallelStream()Most common
ArrayArrays.stream(arr)Primitive overloads
ValuesStream.of(a, b, c)Varargs
RangeIntStream.range(0, n)Half-open
GenerateStream.generate(supplier)Infinite
IterateStream.iterate(seed, fn) / iterate(seed, hasNext, next)Bounded form Java 9+
FilesFiles.lines(path)Auto-closed
BuilderStream.builder().add(x).build()Imperative add
EmptyStream.empty()Avoid null

2. Using Intermediate Operations

OperationStateless?Purpose
filter(Predicate)YesKeep matching
map(Function)Yes1:1 transform
flatMap(Function)Yes1:N flatten
mapMulti Java 16+YesImperative flatten
distinct()StatefulDedup via equals
sorted()StatefulBuffers all elements
peek(Consumer)YesDebug only
limit(n) / skip(n)StatefulSlicing
takeWhile / dropWhileStatefulJava 9+

3. Using Terminal Operations

OperationReturnsShort-circuits
forEach / forEachOrderedvoidNo
collect(Collector)RNo
toList() Java 16+Unmodifiable ListNo
reduceT or Optional<T>No
countlongNo (sized → yes)
min / maxOptionalNo
findFirst / findAnyOptionalYes
anyMatch / allMatch / noneMatchbooleanYes
toArrayObject[] / 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)
      
PropertyMeaning
LazyIntermediate ops do nothing until terminal
Single useStream cannot be reused after terminal
FusionAdjacent stateless ops combined
Encounter orderPreserved unless unordered()

5. Working with Primitive Streams

StreamBoxSpecialized Ops
IntStreamIntegersum, average, summaryStatistics, range
LongStreamLongSame as IntStream
DoubleStreamDoubleSame minus range

Example: Avoid boxing

int total = orders.stream().mapToInt(Order::amount).sum();
IntSummaryStatistics st = orders.stream().mapToInt(Order::amount).summaryStatistics();

6. Using Collectors

CollectorResult
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 / averagingIntAggregation
joining(delim, prefix, suffix)String
mapping / filtering / flatMappingDownstream 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())

ArgumentTypeRole
supplierSupplier<A>New mutable container
accumulatorBiConsumer<A,T>Add element
combinerBinaryOperator<A>Merge for parallel
finisherFunction<A,R>Optional final transform
characteristicsCONCURRENT, UNORDERED, IDENTITY_FINISHHints

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())

AspectDetail
PoolCommon ForkJoinPool (size = cores − 1)
Custom poolSubmit stream task to ForkJoinPool
Good fitLarge N, CPU-bound, splittable source
Bad fitSmall N, blocking I/O, ordered ops
ReduceIdentity 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

FactorImpact
Auto-boxingUse primitive streams
Stateful opsBuffer entire stream (sorted, distinct)
SplitabilityArrayList good; LinkedList poor
Short-circuitPlace selective filters early
Object overheadvs plain for-loop ~ 2-5×

10. Handling Infinite Streams

PatternExample
Bounded by limitStream.iterate(0, i->i+1).limit(100)
Bounded formStream.iterate(0, i->i<100, i->i+1)
takeWhilestream.takeWhile(x -> x < 50)
findFirst on infiniteOK if predicate eventually matches
Warning: Never call count(), collect(), or sorted() on an unbounded stream.

11. Using FlatMap for Nested Structures

VariantMaps Each Element To
flatMapStream<R>
flatMapToInt/Long/DoublePrimitive stream
mapMultiPushes 0..N to consumer

Example: Flatten orders

List<Item> allItems = orders.stream()
    .flatMap(o -> o.items().stream())
    .toList();

12. Debugging Streams (peek())

ToolUse
peekInspect elements between ops
Logger.atDebugConditional logging
IntelliJ Stream TraceVisualize per-stage values
Stream Debugger pluginStep 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+)

MethodBehaviorOrdered Stream
takeWhile(p)Take prefix while p holdsStops at first false
dropWhile(p)Skip prefix while p holdsEmits rest
vs filterPosition-aware vs all elementsDifferent semantics

14. Using Stream.toList() (Java 16+)

AspecttoList()collect(toList())
MutabilityUnmodifiableMutable ArrayList
NullsAllowedAllowed
PerformanceSlightly faster (presized)Baseline
RecommendationDefault choiceUse only when mutability needed