Ответ
Операции в Stream API делятся на промежуточные (intermediate) и терминальные (terminal), что является основой ленивой (lazy) обработки данных.
Промежуточные операции:
- Цель: Преобразовать или отфильтровать поток элементов, создавая новый поток для дальнейшей обработки.
- Поведение: Ленивые. Они не выполняют немедленных вычислений, а лишь добавляют операцию в конвейер (pipeline).
- Возвращаемое значение:
Stream<T>. - Примеры:
filter(Predicate<T>),map(Function<T, R>),sorted(),distinct(),limit(long n),peek(Consumer<T>).
Терминальные операции:
- Цель: Завершить обработку потока, получив конечный результат (значение, коллекцию или побочный эффект).
- Поведение: Энергичные (eager). Вызов терминальной операции запускает выполнение всего накопленного конвейера промежуточных операций.
- Возвращаемое значение: Не
Stream(это может бытьvoid,Optional,List,boolean,longи т.д.). - Примеры:
collect(Collector),forEach(Consumer<T>),reduce(...),count(),anyMatch(Predicate<T>),findFirst().
Ключевое следствие: Поскольку промежуточные операции ленивы, поток данных будет обработан за один проход (при вызове терминальной операции), что может быть эффективнее нескольких итераций по коллекции.
Пример, демонстрирующий ленивость:
List<String> list = Arrays.asList("a1", "b2", "c3", "a2");
list.stream()
.filter(s -> {
System.out.println("filter: " + s); // Вывод появится только после вызова .findFirst()
return s.startsWith("a");
})
.map(s -> {
System.out.println("map: " + s); // Аналогично
return s.toUpperCase();
})
.findFirst(); // Терминальная операция, которая запускает обработку
// Вывод в консоль:
// filter: a1
// map: a1
// Обработка остановилась после нахождения первого подходящего элемента.