flatMap и числовые потоки
map превращает каждый элемент ровно в один новый. Но бывает, что из одного элемента нужно получить несколько — например, из заказа все его товары. Для этого есть flatMap. Во второй части урока подробнее разберём числовые потоки, с которыми вы уже встречались в уроке об операциях.
flatMap
flatMap превращает каждый элемент в поток, а потом склеивает все эти потоки в один. Так список списков становится плоским списком:
void main() {
List<List<String>> groups = List.of(
List.of("Анна", "Олег"),
List.of("Ира"),
List.of("Пётр", "Лена"));
List<String> everyone = groups.stream()
.flatMap(group -> group.stream())
.toList();
IO.println(everyone);
}Результат:
[Анна, Олег, Ира, Пётр, Лена]Вложенные данные
Чаще всего flatMap достаёт коллекцию из каждого объекта. Здесь из заказов собираются все товары без повторов:
record Order(String customer, List<String> items) {}
void main() {
List<Order> orders = List.of(
new Order("Анна", List.of("хлеб", "сыр")),
new Order("Олег", List.of("сыр", "чай")));
List<String> products = orders.stream()
.flatMap(o -> o.items().stream())
.distinct()
.toList();
IO.println(products);
}Результат:
[хлеб, сыр, чай]mapMulti
С Java 16 есть mapMulti — он делает то же, но без создания промежуточных потоков. Лямбда получает элемент и «приёмник» и передаёт в него столько значений, сколько нужно. Тип результата указывают в угловых скобках перед именем метода:
record Line(String product, int quantity) {}
void main() {
List<Line> lines = List.of(new Line("чай", 2), new Line("сыр", 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);
}Результат:
[чай, чай, сыр]IntStream, LongStream, DoubleStream
Числовые потоки хранят примитивы int, long и double без упаковки в объекты и умеют sum(), average(), max(). Их создают через range(), rangeClosed() (включая правую границу) и 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));
}Результат:
5050
2432902008176640000
4.16666666666666720! не помещается в int, поэтому здесь LongStream.
boxed и mapToObj
У числового потока нет toList(). Чтобы собрать числа в список, их упаковывают методом boxed() — он превращает IntStream в Stream<Integer>. А mapToObj() сразу превращает каждое число в объект:
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 -> "Место " + n)
.toList();
IO.println(seats);
}Результат:
[1, 4, 9, 16]
[Место 1, Место 2, Место 3]summaryStatistics
Метод summaryStatistics() за один проход считает количество, сумму, минимум, максимум и среднее. Достают их методами getCount(), getSum(), getMin(), getMax() и 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());
}Результат:
4
24
3 .. 9
6.0Если нужны сразу несколько показателей, это быстрее, чем пять отдельных потоков.
Частая ошибка
Вызвать toList() у IntStream нельзя — такого метода у числовых потоков нет:
void main() {
List<Integer> numbers = IntStream.range(1, 4).toList();
IO.println(numbers);
}Результат:
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 failedДобавьте boxed() перед toList() или соберите массив методом toArray().