Loslegen
Javaneer
Zurück zur Stufe
Stufe 4·Fortgeschrittenes Java

The Stream API

The pipeline model, map/filter/reduce, collectors, and when not to go parallel.

22 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

The Stream API is modern Java's most loved feature. It lets you process collections of data as a pipeline of operations - filter, transform, aggregate - in clear, declarative steps. Once it clicks, you'll never want to write a manual loop again.

The pipeline model

A stream pipeline has three parts:

  1. A source - where the data comes from (a list, a file, a range).
  2. Intermediate operations - transformations like filter and map (lazy; they build up the pipeline).
  3. A terminal operation - like collect or count that triggers processing and produces a result.
A stream is like a factory assembly line

Raw materials (your data) enter one end. Each station on the line does one job - inspect and reject (filter), reshape (map), count or box up (collect). The line only runs when someone at the end asks for the finished product (the terminal operation). Nothing happens until then.

List<String> names = List.of("Ada", "Grace", "Alan", "Bob");

List<String> result = names.stream()          // source
        .filter(n -> n.length() > 3)           // intermediate
        .map(String::toUpperCase)              // intermediate
        .sorted()                              // intermediate
        .toList();                             // terminal

System.out.println(result);                    // [ALAN, GRACE]

Common operations

filter - keep elements matching a condition:

numbers.stream().filter(n -> n % 2 == 0)   // even numbers only

map - transform each element:

words.stream().map(String::length)          // each word -> its length

reduce - combine all elements into one value:

int sum = numbers.stream().reduce(0, Integer::sum);   // add them all up

collect / toList - gather results into a collection:

Set<String> unique = words.stream().collect(Collectors.toSet());

Collectors: powerful aggregation

Collectors do sophisticated grouping and summarizing:

// Group people by their city
Map<String, List<Person>> byCity = people.stream()
        .collect(Collectors.groupingBy(Person::city));

// Join names into one string
String line = names.stream().collect(Collectors.joining(", "));

// Count, average, sum
double avgAge = people.stream()
        .collect(Collectors.averagingInt(Person::age));

Laziness: why it's efficient

Intermediate operations are lazy - they do nothing until a terminal operation runs. This lets streams skip unnecessary work:

Optional<String> first = names.stream()
        .filter(n -> n.startsWith("A"))
        .findFirst();      // stops as soon as the first match is found

Streams don't modify the source

A stream never changes the original collection - it produces a new result. This fits the functional, immutable style: your source data stays intact, and you get a fresh output.

Parallel streams - and when NOT to use them

Adding .parallel() splits work across CPU cores:

long count = hugeList.parallelStream()
        .filter(expensive::test)
        .count();

Parallel is not always faster

Parallel streams add coordination overhead and only help with large datasets and CPU-heavy, independent work. For small collections or I/O-bound tasks, they're often slower - and they can cause subtle bugs if your operations aren't thread-safe. Measure before reaching for .parallel(); default to sequential.

A complete example

record Order(String product, String category, double amount) {}

Map<String, Double> revenueByCategory = orders.stream()
        .filter(o -> o.amount() > 0)
        .collect(Collectors.groupingBy(
                Order::category,
                Collectors.summingDouble(Order::amount)));

In a handful of readable lines, we filtered, grouped, and summed - code that would take a nested loop and several helper variables the imperative way.

Quick check

In a stream pipeline, when do the intermediate operations (filter, map) actually run?

Key takeaways

  • A stream pipeline = source -> intermediate operations -> terminal operation.
  • filter keeps matching elements; map transforms them; reduce combines them; collect gathers results.
  • Collectors enable grouping (groupingBy), joining, counting, and summarizing.
  • Intermediate operations are lazy - nothing runs until a terminal operation is called.
  • Streams never modify their source; they produce new results.
  • Use parallel streams only for large, CPU-bound, independent work - and measure first.

Next, we peek behind the curtain at how frameworks work their magic: annotations and reflection.