Working with Streams and Lambda Expressions
This domain tests lambda target typing, method reference forms, pipeline laziness and collector behaviour.
Lambdas and functional interfaces
A lambda needs a target type: an interface with exactly one abstract method. Forms include x -> x * 2, (a, b) -> a + b, and a block body with explicit return. Method references come in four flavours: static (Integer::parseInt), bound instance (s::length), unbound instance (String::length), and constructor (ArrayList::new).
The stream pipeline
A stream has a source, zero or more intermediate operations, and exactly one terminal operation. Intermediate operations are lazy - nothing runs until the terminal operation is invoked. A stream can be consumed only once; reusing it throws IllegalStateException. map is one-to-one, flatMap flattens nested streams, and peek is an intermediate debugging hook. Terminal operations include forEach, collect, count, reduce, anyMatch, findFirst and toArray.
Primitive streams and Optional
mapToInt, IntStream.range, sum and average avoid boxing; average returns OptionalDouble. Optional models a possibly-absent return value, with orElse, orElseGet, orElseThrow, map, filter and ifPresent.
Collectors
Collectors.toList(), toSet(), joining(", "), groupingBy, partitioningBy and toMap. toMap throws IllegalStateException on a duplicate key unless a merge function is supplied.
Worked example
Stream.of(1, 2, 3).map(i -> i * 2).filter(i -> i > 2).forEach(System.out::print); prints 46. Reasoning: each element flows through the whole pipeline in encounter order, so 1 becomes 2 and is filtered out, 2 becomes 4 and prints, 3 becomes 6 and prints.
Traps
Forgetting the terminal operation so nothing executes; consuming a stream twice; using findFirst when encounter order is irrelevant; modifying the backing collection during a stream.