Что такое промежуточные (конвейерные) операции в Stream API Java?

Ответ

Промежуточные (intermediate) операции в Stream API — это ленивые операции, которые преобразуют один поток в другой, формируя конвейер обработки данных. Они выполняются только при вызове терминальной операции.

Ключевые свойства:

  • Ленивость (Lazy): Вычисление происходит только по требованию терминальной операции.
  • Возвращают новый Stream: Позволяют строить цепочки вызовов.
  • Без состояния / с состоянием: Некоторые операции (как sorted или distinct) требуют знания обо всех элементах потока.

Распространенные промежуточные операции:

Операция Описание Пример
filter(Predicate<T>) Отфильтровывает элементы по условию. .filter(s -> s.length() > 3)
map(Function<T,R>) Преобразует каждый элемент. .map(String::toUpperCase)
sorted() / sorted(Comparator) Сортирует элементы. .sorted(Comparator.reverseOrder())
distinct() Удаляет дубликаты. .distinct()
limit(long n) Ограничивает поток первыми n элементами. .limit(10)
skip(long n) Пропускает первые n элементов. .skip(5)
peek(Consumer<T>) Выполняет действие для каждого элемента (отладка). .peek(System.out::println)

Пример конвейера:

List<String> result = names.stream()          // Источник
        .filter(name -> name.startsWith("A")) // Промежуточная (ленивая)
        .map(String::toUpperCase)             // Промежуточная (ленивая)
        .sorted()                             // Промежуточная (ленивая, с состоянием)
        .collect(Collectors.toList());        // Терминальная (запускает выполнение)

Важно: Пока не вызвана терминальная операция (collect, forEach, reduce), данные не обрабатываются. Это позволяет оптимизировать выполнение (например, объединение нескольких операций filter в один проход).

Ответ 18+ 🔞

А, слушай, про эти ваши стримы в Java! Ну, это ж просто пиздец, как удобно, если понять, как они там, блядь, под капотом шевелятся. Сейчас разжую, как для дебила, но ты не обижайся.

Представь себе, что у тебя есть труба, по которой сыпется хуёвочка разная — данные там, строки, цифры. Так вот, промежуточные операции — это как раз такие хитрые фильтры и преобразователи, которые ты на эту трубу накручиваешь. Но самый прикол в чём? Пока ты в конец трубы ведро не подставишь (это терминальная операция), нихуя не течёт! Всё стоит сухо, блядь. Это и есть их ленивость (Lazy). Они только готовятся, сука, работать, но сами по себе — ни хуя.

Что они умеют, эти мартышлюшки промежуточные?

  • Вернуть новый Stream: Каждая такая операция — это как новый отрезок трубы. Прикрутил filter — получил трубу только для длинных слов. Прикрутил map — получил трубу, где всё уже в верхнем регистре летит. Цепочку строить можно — красота, ёпта!
  • Быть простыми или с памятью: Большинство — пофигисты, работают с элементом и тут же его забывают. Но есть и заумные, типа sorted() или distinct(). Вот эти, блядь, хитрожопые — им надо все элементы сначала увидеть, чтобы отсортировать или дубликаты выкинуть. Такие называются stateful (с состоянием), и они могут производительность подъебать на больших потоках.

Ну и табличка, чтоб не ебать мозг:

Что вызываем Что делает, грубо говоря Пример, как в жизни
filter(...) Отсеивает хуйню, которая не подходит под условие. .filter(s -> s.length() > 3) — оставляет только слова длиннее трёх букв, короткие — нахуй.
map(...) Превращает каждый кусок говна во что-то другое. .map(String::toUpperCase) — все строки делает КРИЧАЩИМИ, БЛЯДЬ.
sorted() Включает внутреннего менеджера и всё раскладывает по порядку. .sorted(Comparator.reverseOrder()) — выстраивает всё задом наперёд, с конца.
distinct() Выкидывает повторы, оставляет только уникальные экземпляры. .distinct() — если два одинаковых элемента приползли, один идёт в пизду.
limit(n) Жадная сволочь. Хватает только первые N штук, остальным — отбой. .limit(10) — «мне только десять, на остальное насрать».
skip(n) Наоборот, первые N штук пропускает, мол, «мелочь, пошла нахуй». .skip(5) — первые пять элементов игнорирует, начинает с шестого.
peek(...) Подсматриватель. Позволяет глянуть на элемент, пока он по трубе летит (обычно для отладки). .peek(System.out::println) — «ой, а что это у нас тут пролетело?».

И вот живой пример, как это всё в коде выглядит:

List<String> result = names.stream()          // Вот она, труба-источник, открыли кран
        .filter(name -> name.startsWith("A")) // Фильтр: только на "А". Но пока тока намерение!
        .map(String::toUpperCase)             // Преобразуем в большие буквы. Всё ещё тишина.
        .sorted()                             // Сортируем. Уже сложнее, но поток ещё не пошёл!
        .collect(Collectors.toList());        // А ВОТ ТУТ — ХУЯК! Подставили ведро (collect). И только сейчас вся цепочка ПРОСНУЛАСЬ и начала пахать!

Запомни главное, чувак: пока в конце не появится что-то жадное (collect, forEach, count), весь этот красивый конвейер из filter-map-sorted — это просто сказка, блядь, нарисованная в воздухе. JVM смотрит на эту цепочку и умно оптимизирует, может несколько операций в один проход слепить. Но начинает она это делать только по твоей команде — терминальной операции. Вот такая магия, ёпта!