Introduced in Java 8, the Stream API revolutionized how developers process collections by enabling functional-style operations like filtering, mapping, and reducing — all while writing concise, readable, and parallelizable code.
What is a Stream?
A Stream is a sequence of elements supporting sequential and parallel aggregate operations. It does not store data, but transforms it using a pipeline of operations.
Streams work best with Java collections, but can also be created from arrays, files, or even generated dynamically.
Stream Pipeline Structure
A stream pipeline consists of:
- Source – Collection, array, etc.
- Intermediate operations – e.g.,
map()
, filter()
, sorted()
- Terminal operation – e.g.,
collect()
, forEach()
, reduce()
Creating a Stream
java
List<String> names = List.of("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
Common Stream Operations
1. filter()
Filters elements based on a predicate.
java
list.stream().filter(s -> s.length() > 3);
2. map()
Transforms elements.
java
list.stream().map(String::toUpperCase);
3. sorted()
Sorts elements.
java
list.stream().sorted();
4. collect()
Collects results into a list, set, or map.
java
List<String> result = list.stream()
.filter(s -> s.contains("a"))
.collect(Collectors.toList());
5. reduce()
Performs a reduction on elements (e.g., sum, product).
java
int sum = numbers.stream()
.reduce(0, Integer::sum);
Parallel Streams
Streams can be executed in parallel for better performance on large datasets:
java
list.parallelStream()
.filter(...).map(...).collect(...);
Note: Use with caution — ensure thread safety and avoid shared mutable state.
Primitive Streams
Specialized stream interfaces for primitives:
IntStream
, LongStream
, DoubleStream
java
IntStream.range(1, 5).sum(); // 1 + 2 + 3 + 4
Best Practices
- Prefer streams for declarative data transformations.
- Avoid using
forEach
for non-terminal logic; use map
, filter
, collect
. - Avoid mixing streams with side-effects (e.g., modifying external variables).
- Use
Optional
with streams to handle possible null values safely.
When Not to Use Streams
- Complex looping logic that’s easier to express with
for
loops - Situations requiring indexed access
- Performance-critical code where stream overhead matters
Java’s Stream API offers a clean and expressive way to handle data transformations. By mastering streams, Java developers can write concise, functional-style code that’s easier to test and maintain.