Ответ
В Java Stream API операции делятся на промежуточные (intermediate) и терминальные (terminal), что определяет их поведение в цепочке вызовов.
Промежуточные операции (например, filter(), map(), sorted(), distinct()):
- Ленивые (lazy) — не выполняют обработку немедленно, а лишь добавляют новую операцию в конвейер.
- Возвращают новый
Stream, что позволяет строить цепочки (pipeline). - Обработка данных начинается только при вызове терминальной операции.
Терминальные операции (например, collect(), forEach(), reduce(), count(), anyMatch()):
- Энергичные (eager) — запускают выполнение всего конвейера, обрабатывают элементы и производят результат или побочный эффект.
- Возвращают не
Stream, а конкретное значение (void,Optional,List,booleanи т.д.). - После вызова терминальной операции поток считается потребленным и не может быть использован повторно.
Пример:
List<String> result = names.stream() // Источник потока
.filter(name -> name.length() > 3) // Промежуточная операция: фильтрация
.map(String::toUpperCase) // Промежуточная операция: преобразование
.collect(Collectors.toList()); // Терминальная операция: сбор в список
В этом примере filter и map лишь настраивают конвейер. Фактический обход коллекции names, применение фильтра и маппера происходит внутри вызова collect().
Ответ 18+ 🔞
А, блядь, смотри, сейчас объясню на пальцах, как эта ваша Stream API работает, а то народ путается, как слепой кот в сортире. Тут всё просто, как три копейки, но если не понять разницу — будет тебе хиросима и нигерсраки в коде.
Представь себе водопроводную трубу, да? Это и есть стрим. Промежуточные операции — это когда ты к этой трубе прикручиваешь всякие штуки: фильтр от ржавчины (.filter()), нагреватель (.map()), или изгиб, чтобы вода крутилась (.sorted()). Ты их навесил, но вода-то ещё не потекла! Ты просто подготовил систему, ёпта. Они ленивые, как мой сосед в воскресенье — сами ничего не делают, только ждут команды. И каждая такая операция возвращает тебе новую, модифицированную трубу (новый Stream), чтобы ты мог дальше прикручивать.
А теперь терминальная операция — это когда ты, наконец, открываешь кран! .collect(), .forEach(), .count(). Вот тут-то всё и начинается: вода хлынула, прошла через все твои фильтры и нагреватели, и на выходе получился конкретный результат — налитая кружка (List), счётчик капель (long) или просто плеск в раковину (void).
И главное, чувак, после того как кран открыл — всё, труба пуста! Повторно из неё не попьёшь. Попробуешь — получишь IllegalStateException, вот это удивление пиздец будет. Поток одноразовый, как зажигалка с барахолки.
Смотри на примере, тут всё видно:
List<String> result = names.stream() // Вот тебе голая труба с именами
.filter(name -> name.length() > 3) // Прикрутил сито: короткие имена — в мусор
.map(String::toUpperCase) // Поставил обогреватель: делает все буквы большими
.collect(Collectors.toList()); // А вот и открыл кран! Собирай результат в ведро (List).
Пока не вызвал collect(), нихуя не происходит. Компьютер смотрит на эту цепочку и думает: «Э, сабака сука, опять эти ваши ленивые стримы собрали... ну ладно, подожду». И ждёт терминальной команды. Как только она пришла — ёперный театр! — запускается весь конвейер разом.
Запомни: lazy (ленивый) — значит «сделаю потом, когда спросите», eager (энергичный) — значит «щас всё сделаю, вот результат, доволен?». И да, не пытайся использовать поток дважды — он не для этого. Сделал дело — создавай новый.