Java Streams API

A stream (Java 8) is a pipeline to process a collection of data in a clear way: take data, filter it, change it and collect the result. It does not change the original list.

The idea

  1. Source: a list, an array, or numbers: list.stream().
  2. Intermediate steps: filter, map, sorted, distinct, limit. They are lazy: nothing runs until the end step.
  3. Terminal step: collect, forEach, count, sum, reduce. This starts the work.

First example

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(5, 12, 8, 3, 20, 15, 10);

        List<Integer> result = numbers.stream()
                .filter(n -> n > 7)              // keep big numbers
                .map(n -> n * 2)                 // double them
                .sorted()
                .toList();                       // Java 16+

        System.out.println(result);
        System.out.println(numbers);             // the original is not changed
    }
}
Output

[16, 20, 24, 30, 40]
[5, 12, 8, 3, 20, 15, 10]

Common operations

import java.util.List;

public class Ops {
    public static void main(String[] args) {
        List<String> names = List.of("riya", "aman", "meera", "karan", "aman", "al");

        System.out.println(names.stream().distinct().count());
        System.out.println(names.stream().filter(n -> n.length() > 3).toList());
        System.out.println(names.stream().map(String::toUpperCase).limit(3).toList());
        System.out.println(names.stream().sorted().skip(1).findFirst().get());
        System.out.println(names.stream().anyMatch(n -> n.startsWith("m")));
        System.out.println(names.stream().allMatch(n -> n.length() > 2));
        System.out.println(names.stream().mapToInt(String::length).sum());
        System.out.println(names.stream().mapToInt(String::length).average().getAsDouble());
    }
}
Output

5
[riya, aman, meera, karan, aman]
[RIYA, AMAN, MEERA]
aman
true
false
24
4.0

Collect and reduce

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

public class Collect {
    public static void main(String[] args) {
        List<String> words = List.of("java", "stream", "api", "lambda");

        String joined = words.stream().collect(Collectors.joining(", ", "[", "]"));
        System.out.println(joined);

        int total = List.of(1, 2, 3, 4).stream().reduce(0, Integer::sum);
        System.out.println(total);

        System.out.println(words.stream().max(java.util.Comparator.comparing(String::length)).get());
    }
}
Output

[java, stream, api, lambda]
10
stream

Group the data

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

record Student(String name, String city, int marks) { }

public class Group {
    public static void main(String[] args) {
        List<Student> students = List.of(
                new Student("Riya", "Delhi", 88),
                new Student("Aman", "Pune", 72),
                new Student("Meera", "Delhi", 95),
                new Student("Karan", "Pune", 60));

        Map<String, List<String>> byCity = students.stream()
                .collect(Collectors.groupingBy(Student::city, TreeMap::new,
                        Collectors.mapping(Student::name, Collectors.toList())));
        System.out.println(byCity);

        Map<String, Double> avg = students.stream()
                .collect(Collectors.groupingBy(Student::city, TreeMap::new, Collectors.averagingInt(Student::marks)));
        System.out.println(avg);

        System.out.println(students.stream().filter(s -> s.marks() >= 80).map(Student::name).toList());
    }
}
Output

{Delhi=[Riya, Meera], Pune=[Aman, Karan]}
{Delhi=91.5, Pune=66.0}
[Riya, Meera]

Streams of numbers

import java.util.stream.IntStream;

public class Range {
    public static void main(String[] args) {
        System.out.println(IntStream.rangeClosed(1, 5).sum());
        System.out.println(IntStream.range(0, 5).boxed().toList());
        System.out.println(IntStream.rangeClosed(1, 20).filter(n -> n % 3 == 0).boxed().toList());
    }
}
Output

15
[0, 1, 2, 3, 4]
[3, 6, 9, 12, 15, 18]

A stream can be used only once

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

public class Once {
    public static void main(String[] args) {
        Stream<String> s = List.of("a", "b").stream();
        System.out.println(s.count());
        System.out.println(s.count());
    }
}
Output

2
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:260)
at java.base/java.util.stream.ReferencePipeline.count(ReferencePipeline.java:750)
at Once.main(Once.java:8)

Rules

  • Streams do not store data and do not change the source.
  • Do not change outside variables inside a stream (side effects).
  • Use parallelStream() only for big data and heavy work, and measure first.
Note: Use a stream when it makes the code clearer. A simple loop is still fine and sometimes better. Next: Java Optional.

Congratulations! Chapter Finished. Learn more about the similar topics:
Exercises & Assignments
No Content Found.
Interview Questions & Answers
No Content Found.