Перейти к основному содержимому

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.


Типичные ошибки

  1. Повторное использование потока — один Stream можно потребить только один раз.
  2. peek для логики — только отладка; бизнес-логику не размещайте в peek.
  3. Изменение внешних переменных в лямбде — переменные из замыкания должны быть effectively final.
  4. null в stream — приведёт к NPE в map; фильтруйте или используйте Optional.

Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).