Stream API
Stream — одноразовый конвейер вычислений над последовательностью элементов. Он не хранит данные, а получает их из источника: коллекции, массива, генератора, файла или другого stream.
Структура конвейера
источник → промежуточные операции → терминальная операцияList<String> result = users.stream()
.filter(User::active)
.map(User::name)
.filter(name -> !name.isBlank())
.sorted()
.toList();Промежуточные операции возвращают новый stream и обычно ленивы. Работа
начинается после терминальной операции. Элементы часто проходят конвейер
по одному: filter может сразу передать элемент в map, не создавая
промежуточной коллекции.
Подробнее: Промежуточные операции и Терминальные операции.
Stream и Collection
| Collection | Stream |
|---|---|
| хранит элементы | описывает вычисление |
| можно обходить повторно | после терминальной операции закрыт |
| допускает изменение содержимого | операции не должны менять источник |
| eager API хранения | большая часть pipeline ленива |
Повторное использование заканчивается IllegalStateException:
Stream<String> stream = names.stream();
long count = stream.count();
// stream.findFirst(); // stream has already been operated upon or closedСоздание
Stream.of("a", "b");
Arrays.stream(values);
collection.stream();
IntStream.range(0, 10);
Stream.iterate(1, n -> n <= 100, n -> n * 2);
Stream.generate(UUID::randomUUID).limit(5);Для IntStream, LongStream, DoubleStream есть числовые операции без
boxing каждого элемента. Переходы: mapToInt, boxed.
Побочные эффекты
Функции pipeline должны быть stateless и non-interfering: не зависеть от изменяемого общего состояния и не менять источник во время обхода.
// Плохо: общий mutable accumulator
List<String> output = new ArrayList<>();
names.parallelStream().forEach(output::add);
// Результат выражен самой операцией
List<String> output = names.parallelStream().map(String::trim).toList();Parallel stream
parallel() использует разбиение источника и обычно общий
ForkJoinPool.commonPool. Параллельность выгодна не автоматически: мешают
малый объём, дешёвые операции, плохое разбиение, блокирующий I/O и общий state.
Особенно важны ассоциативность reduce и корректный collector.
Ресурсные stream (Files.lines и подобные) закрывают через
try-with-resources. Для обычного collection stream закрытие ничего не делает.
Связанные темы: Iterable, Comparator Java, Обобщения generics.
Вопросы для собеседования
- Хранит ли stream элементы? Нет, он потребляет источник.
- Когда выполняется filter? При запуске терминальной операции.
- Можно ли использовать stream дважды? Нет.
- Чем map отличается от flatMap? map даёт один результат на элемент, flatMap разворачивает вложенный stream.
- Почему побочные эффекты опасны? Они ломают независимость элементов и особенно заметны при parallel.
- Всегда ли parallel быстрее? Нет, накладные расходы могут быть больше работы.
- Зачем primitive streams? Чтобы выполнять числовые операции без лишнего boxing.