Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

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.166666666666667

20! не помещается в 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().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.