Shiklo Programming
EN
  • English
  • Español
  • Português
  • Deutsch
  • Français
  • 日本語
  • हिन्दी

Java Streams

A stream is a way to process a sequence of values step by step, like an assembly line: keep only some items, change each one, sort them, then collect or count the result. Each step takes a lambda. Streams let you say what you want instead of writing the loops yourself, and the code often reads like a sentence.

A first pipeline

A stream pipeline has a source (often a list), intermediate steps that return a new stream, and one terminal step that produces the result:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(5, 12, 8, 21, 3, 16, 10);
        List<Integer> result = numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * n)
                .sorted()
                .toList();
        System.out.println(result);
        System.out.println(numbers);
    }
}
Output
[64, 100, 144, 256]
[5, 12, 8, 21, 3, 16, 10]

filter keeps the even numbers, map squares each one, sorted sorts them and toList collects them into a new list. The original list does not change. Nothing happens until the terminal step runs.

Counting, summing and averaging

Number streams (IntStream) have ready-made terminal steps. mapToInt turns a stream of objects into one:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("stream", "map", "filter", "lambda", "java");
        System.out.println(words.stream().filter(w -> w.length() > 4).count());
        System.out.println(words.stream().mapToInt(String::length).sum());
        System.out.println(words.stream().mapToInt(String::length).average().orElse(0));
        System.out.println(words.stream().anyMatch(w -> w.startsWith("j")));
    }
}
Output
3
25
5.0
true

Optional results

Some terminal steps may have no answer, such as the maximum of an empty stream. They return an Optional, a box that is either empty or holds one value. orElse gives a default when it is empty:

import java.util.List;
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Anjali", "Bo", "Christopher");
        Optional<String> longest = names.stream().max((a, b) -> a.length() - b.length());
        System.out.println(longest.get());
        Optional<String> withZ = names.stream().filter(n -> n.contains("z")).findFirst();
        System.out.println(withZ.isPresent());
        System.out.println(withZ.orElse("nobody"));
    }
}
Output
Christopher
false
nobody

Grouping with Collectors

collect with a collector builds maps and texts. Collectors.groupingBy puts items into groups, and Collectors.joining joins strings:

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 Main {
    public static void main(String[] args) {
        List<Student> students = List.of(
                new Student("Asha", "Pune", 82), new Student("Bilal", "Agra", 67),
                new Student("Chen", "Pune", 91), new Student("Diya", "Agra", 74));

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

        String top = students.stream()
                .filter(s -> s.marks() >= 75)
                .map(Student::name)
                .collect(Collectors.joining(", ", "Top: ", "."));
        System.out.println(top);
    }
}
Output
{Agra=[Bilal, Diya], Pune=[Asha, Chen]}
Top: Asha, Chen.

TreeMap::new asks for a sorted map, so the cities are printed in alphabetical order.

Streams without a collection

Streams can also be made from a range of numbers or from values directly:

import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        System.out.println(IntStream.rangeClosed(1, 5).map(i -> i * i).sum());
        Stream.of("x", "y", "z").map(String::toUpperCase).forEach(System.out::print);
        System.out.println();
    }
}
Output
55
XYZ

Use streams for clear, step-by-step processing of collections. A plain for loop is still fine, and sometimes clearer, for simple jobs.

Practice exercises

  1. From a list of numbers, print the sum of those greater than 10.
  2. From a list of words, make a list of their lengths.
  3. Group a list of words by their first letter with Collectors.groupingBy.