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()vscollect(Collectors.toList())(иммутабельность). Для middle+ уровня — расскажите про Gatherers: windowFixed/windowSliding для оконных операций и mapConcurrent для параллельного I/O с Virtual Threads.