Traditional iteration with loops is imperative and verbose — you tell the computer how to iterate (index variables, loop conditions) rather than what to compute. This leads to boilerplate code and makes parallel processing difficult. Collections needed a declarative, functional approach.
Lambda expressions provide concise syntax for anonymous functions: (parameters) -> expression. The Stream API processes collections in a functional pipeline: source → intermediate operations (filter, map, sorted) → terminal operation (collect, forEach, reduce). Method references (Class::method) provide even shorter syntax for simple lambdas.
A stream represents a sequence of elements supporting sequential and parallel aggregate operations. Streams are lazy — intermediate operations are not executed until a terminal operation is invoked. The pipeline can be parallelized by calling .parallelStream() instead of .stream().
- Declarative: Focus on what, not how — express intent directly
- Lazy evaluation: Intermediate operations execute only when a terminal operation is invoked
- Parallelism:
parallelStream()splits work across multiple threads automatically - Immutability: Streams do not modify the source collection
- Built from: Java Interfaces — lambdas target functional interfaces (Runnable, Comparator, custom)
- Built from: Java Collections Framework — streams originate from collections
- Builds into: Java Multithreading — parallelStream() enables easy parallel processing
- Contrasts with: Java Loops — declarative vs imperative iteration
- Stream reuse: A stream cannot be reused after a terminal operation — create a new one
- Stateful lambdas: Avoid mutable state in lambda bodies (not thread-safe)
- Performance: Streams have overhead vs loops for simple operations — use for complex pipelines
- parallelStream() pitfalls: Shared mutable state in parallel streams causes data races