← Back to Java series
πŸ“¦
Collections & Generics
Collections & Generics Β· Prerequisite: previous lecture

14. Stream API Introduction

The Stream API (JDK 8+) lets you express collection operations **declaratively**. This lecture covers `filter` / `map` / `reduce`, intermediate vs terminal operations, and `Collectors`.

JavaStreamfiltermapreduceCollectors
Duration
⏱ ~1.5-2 hours
Level
πŸ“Š Intermediate
Prerequisite
🎯 Previous lecture or equivalent knowledge
OUTCOME
The Stream API (JDK 8+) lets you express collection operations **declaratively**. This lecture covers `filter` / `map` / `reduce`, intermediate vs terminal operations, and `Collectors`.

What you'll learn

  • 1Distinguish intermediate from terminal operations
  • 2Combine `filter` / `map` / `sorted` / `reduce`
  • 3Convert results with `Collectors.toList`, `groupingBy`
  • 4Understand lazy evaluation

Overview

The Stream API (JDK 8+) lets you express collection operations **declaratively**. This lecture covers `filter` / `map` / `reduce`, intermediate vs terminal operations, and `Collectors`.

Core Concepts

1) Creating a stream

java
import java.util.List;
import java.util.stream.Stream;

List.of(1, 2, 3).stream();              // from a collection
Stream.of("a", "b", "c");               // from values
java.util.stream.IntStream.range(0, 5); // primitive int stream

2) Intermediate vs terminal

  • **Intermediate**: `filter` / `map` / `sorted` / `distinct` β€” return another `Stream`, lazy
  • **Terminal**: `forEach` / `count` / `collect` / `reduce` β€” trigger evaluation

3) `filter` / `map` / `collect`

java
import java.util.List;
import java.util.stream.Collectors;

List<String> result = List.of(1, 2, 3, 4, 5).stream()
    .filter(n -> n % 2 == 0)
    .map(n -> "#" + n)
    .collect(Collectors.toList());
System.out.println(result);   // [#2, #4]

4) `reduce`

java
int sum = List.of(1, 2, 3, 4).stream()
    .reduce(0, Integer::sum);    // 10

5) `Collectors.groupingBy`

java
Map<Integer, List<String>> byLen = List.of("a", "bb", "cc", "ddd").stream()
    .collect(Collectors.groupingBy(String::length));
System.out.println(byLen);   // {1=[a], 2=[bb, cc], 3=[ddd]}

Examples

Example 1 β€” `BasicStream.java`

java
import java.util.List;

public class BasicStream {
    public static void main(String[] args) {
        var nums = List.of(1, 2, 3, 4, 5);

        long even = nums.stream().filter(n -> n % 2 == 0).count();
        int sum = nums.stream().mapToInt(Integer::intValue).sum();

        System.out.println("even=" + even);
        System.out.println("sum=" + sum);
    }
}

**Output**

text
even=2
sum=15

Example 2 β€” `FilterMapCollect.java`

java
import java.util.List;
import java.util.stream.Collectors;

public class FilterMapCollect {
    public static void main(String[] args) {
        var result = List.of("apple", "banana", "cherry", "fig").stream()
            .filter(s -> s.length() > 3)
            .map(String::toUpperCase)
            .sorted()
            .collect(Collectors.toList());
        System.out.println(result);
    }
}

**Output**

text
[APPLE, BANANA, CHERRY]

Example 3 β€” `Reduce.java`

java
import java.util.List;

public class Reduce {
    public static void main(String[] args) {
        int sum = List.of(1, 2, 3, 4, 5).stream()
            .reduce(0, Integer::sum);
        int max = List.of(3, 1, 4, 1, 5, 9, 2).stream()
            .reduce(Integer.MIN_VALUE, Integer::max);
        System.out.println("sum=" + sum);
        System.out.println("max=" + max);
    }
}

**Output**

text
sum=15
max=9

Example 4 β€” `Grouping.java`

java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Grouping {
    record Person(String name, int age) {}

    public static void main(String[] args) {
        var people = List.of(
            new Person("Jisoo", 21),
            new Person("Minsu", 25),
            new Person("Hana", 21),
            new Person("Tom", 30)
        );

        Map<Integer, List<String>> byAge = people.stream()
            .collect(Collectors.groupingBy(
                Person::age,
                Collectors.mapping(Person::name, Collectors.toList())));

        byAge.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

**Output**

text
21 -> [Jisoo, Hana]
25 -> [Minsu]
30 -> [Tom]

Common Mistakes

  1. Reusing a stream after a terminal operation β†’ `IllegalStateException`
  2. Forgetting that intermediate operations are lazy β€” nothing runs without a terminal
  3. Mutating an external variable inside `map` / `filter` (use stateless lambdas)
  4. Using `forEach` with side effects when you really want `collect`
  5. Reaching for `parallelStream` blindly β€” overhead can exceed any gain

Summary

  • Streams compose **intermediate** ops on top of each other, then run on a **terminal**
  • `filter` / `map` / `collect` covers most needs
  • `Collectors` (especially `groupingBy`) is incredibly flexible

Practice

# Practice - 14. Stream API

## Exercise 1 β€” Average of even numbers

  • File: `Homework01.java`
  • Key concepts: `filter`, `mapToInt`, `average`

Requirements

  • From `{1..10}` compute the average of the even numbers.

Expected output

text
avg=6.0

## Exercise 2 β€” Group people by age

  • File: `Homework02.java`
  • Key concepts: `groupingBy`

Requirements

  • Given 4 `Person(name, age)` records, group by age and print.

Expected output

text
20 -> [A]
21 -> [B, C]
30 -> [D]

## Solutions After trying it yourself, compare with [`answer/`](./answer/).

Solution code (homework/answer/)

answer/Homework01.java

java
import java.util.stream.IntStream;

/** Average of even numbers. */
public class Homework01 {
    public static void main(String[] args) {
        double avg = IntStream.rangeClosed(1, 10)
            .filter(n -> n % 2 == 0)
            .average()
            .orElse(0);
        System.out.println("avg=" + avg);
    }
}

answer/Homework02.java

java
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/** Group people by age. */
public class Homework02 {
    record Person(String name, int age) {}

    public static void main(String[] args) {
        var people = List.of(
            new Person("A", 20),
            new Person("B", 21),
            new Person("C", 21),
            new Person("D", 30));

        Map<Integer, List<String>> byAge = people.stream()
            .collect(Collectors.groupingBy(
                Person::age,
                TreeMap::new,
                Collectors.mapping(Person::name, Collectors.toList())));

        byAge.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

Try It Yourself

bash
cd 03_collections/14_stream/src
javac FilterMapCollect.java
java FilterMapCollect

Next Lecture

[15_Exception](../../04_μ˜ˆμ™Έ_μž…μΆœλ ₯/15_μ˜ˆμ™Έ_처리/) β€” exception handling.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—