Skip to content
Streams API
step 1/7

Reading — step 1 of 7

Learn

~2 min readGenerics, Streams, and Functional Java

The Streams API (Java 8+) is Java's answer to functional collection processing. Streams are lazy, declarative, and parallelizable.

The pipeline pattern

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sumOfEvenSquares = nums.stream()
    .filter(n -> n % 2 == 0)
    .mapToInt(n -> n * n)
    .sum();
// 4 + 16 + 36 + 64 + 100 = 220

Three stages:

  1. Sourcestream(), Stream.of(...), Files.lines(path), etc.
  2. Intermediate operationsfilter, map, flatMap, sorted, distinct, limit, skip, peek. LAZY — no work done until terminal.
  3. Terminal operationcollect, forEach, reduce, count, findFirst, anyMatch, toArray, sum. Triggers the pipeline.

Common collectors

import static java.util.stream.Collectors.*;

List<String> upper = list.stream().map(String::toUpperCase).collect(toList());
Set<String> uniq = list.stream().collect(toSet());
Map<String, Integer> byLen = list.stream().collect(toMap(s -> s, String::length));
String joined = list.stream().collect(joining(", ", "[", "]"));
Map<Boolean, List<Integer>> parts = nums.stream()
    .collect(partitioningBy(n -> n > 5));
Map<String, List<User>> byCity = users.stream()
    .collect(groupingBy(User::getCity));
Map<String, Long> counts = words.stream()
    .collect(groupingBy(Function.identity(), counting()));

Primitive streams

stream() produces Stream<T> (boxed). For primitives, use specialized streams:

IntStream.range(1, 100)
    .filter(n -> n % 7 == 0)
    .sum();                            // returns int

int[] arr = {1, 2, 3};
Arrays.stream(arr).average();          // OptionalDouble
Arrays.stream(arr).max();              // OptionalInt

Memory and speed wins by avoiding boxing.

Parallelism

long total = bigList.parallelStream()
    .mapToLong(this::expensiveCompute)
    .sum();

Uses the common ForkJoinPool. Use sparingly:

  • Only worth it for CPU-heavy operations on large datasets
  • The default thread pool is shared — don't block on I/O
  • Collectors.toList() doesn't preserve order; use toUnmodifiableList() for immutable

When to use streams

Yes:

  • Map/filter/reduce pipelines on collections
  • Aggregations (group, partition, count)
  • Lazy evaluation chains (limit, skip, takeWhile)

No:

  • Trivial loops — a for is clearer
  • Mutating shared state from inside the pipeline
  • Anything where readability suffers — streams should make code clearer, not impressive

Common mistakes

  • Reusing a stream — streams are single-use. After a terminal op, the stream is closed.
  • Side effects inside intermediate ops — breaks parallelization assumptions.
  • forEach for side effects on order — ordering not guaranteed in parallel streams.
  • Stream over a collection that's being modified — concurrent modification exception.
  • map when you want flatMapmap of a function returning Stream gives Stream<Stream<T>>; flatMap unrolls.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…