Stream API в Java
Stream API
★ Stream API (пакет java.util.stream, с Java 8) — способ описать что сделать с последовательностью элементов, не переписывая циклы вручную. Поток не хранит данные: он читает источник (коллекцию, массив, генератор) и прогоняет элементы через цепочку операций.
Базовые материалы: коллекции, особенности языка (лямбды), справочник.
Поток и pipeline
Операции делятся на:
| Тип | Примеры | Результат |
|---|---|---|
| Промежуточные (lazy) | filter, map, flatMap, sorted, distinct, limit | Новый Stream |
| Терминальные | collect, forEach, reduce, count, findFirst, anyMatch | Значение или void |
Пока не вызвана терминальная операция, вычислений нет — это ленивое выполнение.
List<String> active = users.stream()
.filter(User::isActive)
.map(User::getEmail)
.sorted()
.toList(); // Java 16+: неизменяемый список
Эквивалент с collect:
List<String> active = users.stream()
.filter(User::isActive)
.map(User::getEmail)
.sorted()
.collect(Collectors.toList());
Источники потока
List<String> fromList = List.of("a", "b").stream().toList();
IntStream.range(0, 10).forEach(System.out::println);
Stream<String> lines = Files.lines(Path.of("data.txt")); // закрывать try-with-resources
Stream.iterate(0, n -> n + 1).limit(5);
Для примитивов есть IntStream, LongStream, DoubleStream — меньше boxing и выше скорость на больших объёмах.
Частые операции
filter — отбор по условию.
map — преобразование элемента.
flatMap — «разворот» вложенных структур (список списков → один список).
List<String> tokens = sentences.stream()
.flatMap(s -> Arrays.stream(s.split("\\s+")))
.map(String::toLowerCase)
.distinct()
.toList();
reduce — свёртка к одному значению:
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
// или
Optional<Integer> opt = numbers.stream().reduce(Integer::sum);
Collectors
Collectors группирует результаты сложнее, чем toList:
Map<String, List<User>> byCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
String names = users.stream()
.map(User::getName)
.collect(Collectors.joining(", "));
Double avgAge = users.stream()
.collect(Collectors.averagingInt(User::getAge));
Для неизменяемых коллекций в новом коде предпочтительны терминальные методы потока: toList(), toSet(), toMap() (Java 10+ / 16+ с оговорками по дубликатам ключей).
Optional
Optional<T> — контейнер «значение есть или нет», часто возвращается findFirst, reduce, max.
Optional<User> first = users.stream()
.filter(u -> u.getRole().equals("ADMIN"))
.findFirst();
String name = first.map(User::getName).orElse("unknown");
Не используйте Optional в полях сущностей и не передавайте null в Optional.of — только Optional.ofNullable.
Функциональные интерфейы
| Интерфейс | Метод | Назначение |
|---|---|---|
Predicate<T> | test | Условие (filter) |
Function<T,R> | apply | Преобразование (map) |
Consumer<T> | accept | Побочное действие (forEach) |
Supplier<T> | get | Поставка значения (supplyAsync) |
Ссылка на метод (User::getName) — сокращение для лямбды, когда передаётся существующий метод.
Параллельные потоки
collection.parallelStream() делит работу по нескольким потокам ОС.
Имеет смысл при:
- большом объёме данных;
- «тяжёлой» чистой функции в
map/filter; - отсутствии общей изменяемой состояния.
Риски:
- порядок элементов не гарантирован без
forOrdered; - гонки при записи в общие структуры;
- на малых списках часто медленнее из-за накладных расходов.
Для I/O-bound задач лучше асинхронные API (CompletableFuture, virtual threads), а не parallelStream.
Stream или обычный цикл?
| Ситуация | Рекомендация |
|---|---|
| Читаемая цепочка преобразований | Stream |
Несколько break/continue, сложная логика | for |
| Примитивы, горячий участок кода | for или IntStream |
| Побочные эффекты в каждой итерации | Явный цикл; в stream — только forEach с осторожностью |
Stream не заменяет коллекции: после collect вы снова работаете с List/Map.
Типичные ошибки
- Повторное использование потока — один
Streamможно потребить только один раз. peekдля логики — только отладка; бизнес-логику не размещайте вpeek.- Изменение внешних переменных в лямбде — переменные из замыкания должны быть effectively final.
nullв stream — приведёт к NPE вmap; фильтруйте или используйтеOptional.
Связанные материалы
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Основы Java - устройство JDK/JVM, модель компиляции и базовые принципы платформонезависимого выполнения. Java — это объектно-ориентированный язык программирования общего назначения, который работает на принципах «напиши один раз, запускай в любом месте». Набор советов, правил, принципов и обычаев в разработке на этом языке. История Java — от проекта Green и Oak до OpenJDK, LTS-релизов и современной платформы (модули, records, виртуальные потоки). Библиотеки, фреймворки, инструменты сборки, тестирования, развёртывания и мониторинга. Что такое пакет и пакетная структура, как собираются проекты на Java. Справочник-шпаргалка по конфигурациям в Java — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Гайд по установке и настройке с написанием первой программы и её запуском. Практические примеры — консольные утилиты, композиция классов в мини-игре и первое Swing-приложение. Точки останова, пошаговое выполнение, панели Variables и Call Stack — практика отладки в IntelliJ IDEA. Кавычки, точки, запятые, скобки и прочие знаки препинания. Это полный справочник всех ключевых слов языка Java, включая основные, контекстные и зарезервированные слова.Основы языка Java
Что требуется знать перед началом изучения языка программирования Java
Рекомендации по разработке на Java
История языка Java
Экосистема Java-приложений
Структура и сборки Java-проектов
Справочник по конфигурациям в Java
Первая программа на Java
Простые приложения на Java
Отладка Java-кода в IDE
Синтаксис и пунктуация в Java
Ключевые слова в Java