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:
- Source —
stream(),Stream.of(...),Files.lines(path), etc. - Intermediate operations —
filter,map,flatMap,sorted,distinct,limit,skip,peek. LAZY — no work done until terminal. - Terminal operation —
collect,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; usetoUnmodifiableList()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
foris 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.
forEachfor side effects on order — ordering not guaranteed in parallel streams.- Stream over a collection that's being modified — concurrent modification exception.
mapwhen you wantflatMap—mapof 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…