Gymterview
middle

Что нового в Stream API от Java 11 до Java 25?

Stream API эволюционировал от добавления toList() (Java 16) и mapMulti() до полноценных расширяемых промежуточных операций через Stream Gatherers (Java 24/25).

Эволюция Stream API

Версия Нововведение Описание
Java 16 toList() Неизменяемый список, замена collect(Collectors.toList())
Java 16 mapMulti() Замена flatMap для простых случаев (0-N результатов)
Java 24/25 Stream Gatherers Пользовательские промежуточные операции
Пример: toList(), mapMulti()
// Java 16: Stream.toList() — неизменяемый список
List<String> names = users.stream()
    .map(User::getName)
    .toList(); // вместо .collect(Collectors.toList())

// Java 16: Stream.mapMulti() — замена flatMap для простых случаев
Stream.of(1, 2, 3, 4)
    .<Integer>mapMulti((n, consumer) -> {
        if (n % 2 == 0) {
            consumer.accept(n);
            consumer.accept(n * 10);
        }
    })
    .toList(); // [2, 20, 4, 40]

Stream Gatherers (Java 24/25)

Gatherers позволяют создавать stateful промежуточные операции — то, что ранее было невозможно стандартными средствами Stream API.

Пример
// Встроенные Gatherers:
// windowFixed — фиксированные окна
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6)
    .gather(Gatherers.windowFixed(3))
    .toList(); // [[1, 2, 3], [4, 5, 6]]

// windowSliding — скользящее окно
List<List<Integer>> sliding = Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowSliding(3))
    .toList(); // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

// scan — промежуточные результаты свёртки
List<Integer> runningSum = Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList(); // [1, 3, 6, 10, 15]

// mapConcurrent — параллельная обработка с Virtual Threads
List<String> results = urls.stream()
    .gather(Gatherers.mapConcurrent(10, url -> fetchUrl(url)))
    .toList(); // до 10 параллельных запросов
Пример: custom Gatherer — distinctBy
// Пример: distinctBy — unique по ключу
static <T, K> Gatherer<T, ?, T> distinctBy(Function<T, K> keyExtractor) {
    return Gatherer.ofSequential(
        HashSet<K>::new,
        (state, element, downstream) -> {
            K key = keyExtractor.apply(element);
            if (state.add(key)) {
                return downstream.push(element);
            }
            return true;
        }
    );
}

// Использование:
users.stream()
    .gather(distinctBy(User::getEmail))
    .toList();

Частые ошибки

  • Мутация списка из toList()UnsupportedOperationException; если нужен мутабельный — collect(Collectors.toList())
  • mapConcurrent для CPU-bound — Virtual Threads не ускоряют CPU-bound; используйте parallelStream()
  • Custom Gatherer без proper state management — Gatherer может быть parallel; state должен быть thread-safe или sequential

Как используется в 2026

  • toList() — повсеместно заменяет collect(Collectors.toList())
  • Gatherers — новый инструмент для сложных потоковых обработок (окна, группировки, stateful)
  • mapConcurrent — удобная параллелизация I/O-задач в stream

На собеседовании: обязательно знайте разницу toList() vs collect(Collectors.toList()) (иммутабельность). Для middle+ уровня — расскажите про Gatherers: windowFixed/windowSliding для оконных операций и mapConcurrent для параллельного I/O с Virtual Threads.