flatMap and numeric streams
map turns each element into exactly one new element. But sometimes one element has to produce several — for example, all the items of an order. That is what flatMap is for. In the second part of the lesson we look more closely at numeric streams, which you already met in the stream operations lesson.
flatMap
flatMap turns each element into a stream and then glues all those streams into one. This way a list of lists becomes a flat list:
void main() {
List<List<String>> groups = List.of(
List.of("Anna", "Oleg"),
List.of("Ira"),
List.of("Peter", "Lena"));
List<String> everyone = groups.stream()
.flatMap(group -> group.stream())
.toList();
IO.println(everyone);
}Output:
[Anna, Oleg, Ira, Peter, Lena]Nested data
Most often flatMap pulls a collection out of each object. Here all the products are collected from the orders, without duplicates:
record Order(String customer, List<String> items) {}
void main() {
List<Order> orders = List.of(
new Order("Anna", List.of("bread", "cheese")),
new Order("Oleg", List.of("cheese", "tea")));
List<String> products = orders.stream()
.flatMap(o -> o.items().stream())
.distinct()
.toList();
IO.println(products);
}Output:
[bread, cheese, tea]mapMulti
Since Java 16 there is mapMulti — it does the same but without creating intermediate streams. The lambda receives an element and a "sink" and passes as many values to it as needed. The result type is given in angle brackets before the method name:
record Line(String product, int quantity) {}
void main() {
List<Line> lines = List.of(new Line("tea", 2), new Line("cheese", 1));
List<String> units = lines.stream()
.<String>mapMulti((line, sink) -> {
for (int i = 0; i < line.quantity(); i++) {
sink.accept(line.product());
}
})
.toList();
IO.println(units);
}Output:
[tea, tea, cheese]IntStream, LongStream, DoubleStream
Numeric streams hold the primitives int, long and double without boxing them into objects, and they have sum(), average() and max(). They are created with range(), rangeClosed() (which includes the upper bound) and of():
void main() {
IO.println(IntStream.rangeClosed(1, 100).sum());
long factorial = LongStream.rangeClosed(1, 20).reduce(1, (a, b) -> a * b);
IO.println(factorial);
IO.println(DoubleStream.of(4.5, 3.0, 5.0).average().orElse(0));
}Output:
5050
2432902008176640000
4.16666666666666720! does not fit into an int, which is why a LongStream is used here.
boxed and mapToObj
A numeric stream has no toList(). To collect the numbers into a list, box them with boxed() — it turns an IntStream into a Stream<Integer>. And mapToObj() turns each number straight into an object:
void main() {
List<Integer> squares = IntStream.rangeClosed(1, 4)
.map(n -> n * n)
.boxed()
.toList();
IO.println(squares);
List<String> seats = IntStream.rangeClosed(1, 3)
.mapToObj(n -> "Seat " + n)
.toList();
IO.println(seats);
}Output:
[1, 4, 9, 16]
[Seat 1, Seat 2, Seat 3]summaryStatistics
summaryStatistics() computes the count, sum, minimum, maximum and average in a single pass. You read them with getCount(), getSum(), getMin(), getMax() and getAverage():
void main() {
IntSummaryStatistics stats = IntStream.of(7, 3, 9, 5).summaryStatistics();
IO.println(stats.getCount());
IO.println(stats.getSum());
IO.println(stats.getMin() + " .. " + stats.getMax());
IO.println(stats.getAverage());
}Output:
4
24
3 .. 9
6.0When you need several figures at once, this is faster than five separate streams.
Common mistake
You cannot call toList() on an IntStream — numeric streams do not have that method:
void main() {
List<Integer> numbers = IntStream.range(1, 4).toList();
IO.println(numbers);
}Output:
Main.java:2: error: cannot find symbol
List<Integer> numbers = IntStream.range(1, 4).toList();
^
symbol: method toList()
location: interface IntStream
1 error
error: compilation failedAdd boxed() before toList(), or collect an array with toArray().