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);
}
}[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")));
}
}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"));
}
}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);
}
}{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();
}
}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
- From a list of numbers, print the sum of those greater than 10.
- From a list of words, make a list of their lengths.
- Group a list of words by their first letter with
Collectors.groupingBy.