Какие основные промежуточные операции есть в Stream API Java?

Ответ

Промежуточные операции в Stream API (lazy operations) преобразуют один поток в другой и могут быть объединены в цепочку. Основные из них:

Операция Сигнатура Назначение
filter Stream<T> filter(Predicate<? super T>) Фильтрует элементы по условию (predicate).
map <R> Stream<R> map(Function<? super T, ? extends R>) Преобразует каждый элемент (1 к 1).
flatMap <R> Stream<R> flatMap(Function<? super T, ? extends Stream<R>>) Преобразует элемент в поток и "склеивает" все потоки в один (1 ко многим).
distinct Stream<T> distinct() Удаляет дубликаты (использует equals()).
sorted Stream<T> sorted() / sorted(Comparator) Сортирует элементы (естественным порядком или компаратором).
peek Stream<T> peek(Consumer<? super T>) Выполняет действие для каждого элемента (часто для отладки), не изменяя элементы.
limit Stream<T> limit(long maxSize) Ограничивает поток первыми N элементами.
skip Stream<T> skip(long n) Пропускает первые N элементов.

Практический пример:

List<String> names = Arrays.asList("John", "Alice", "Bob", "Anna", "Alex");

List<String> result = names.stream()
        .filter(name -> name.length() > 3)          // Оставляем имена длиннее 3 букв: [John, Alice, Anna, Alex]
        .map(String::toUpperCase)                   // Преобразуем в верхний регистр: [JOHN, ALICE, ANNA, ALEX]
        .sorted()                                   // Сортируем: [ALEX, ALICE, ANNA, JOHN]
        .distinct()                                 // Здесь дубликатов нет, но операция актуальна
        .collect(Collectors.toList());              // Терминальная операция — собираем в список

System.out.println(result); // Вывод: [ALEX, ALICE, ANNA, JOHN]

Все эти операции являются "ленивыми" — вычисления происходят только после вызова терминальной операции (например, collect, forEach).

Ответ 18+ 🔞

Смотри, вот тебе вся эта магия с потоками, но без этой охуевшей академической зауми. Представь, что у тебя есть мешок с данными, и ты его как будто просеиваешь через сито, а потом ещё и красиво упаковываешь.

Вот эти ленивые операции — они как раз и есть твои сита, фильтры и трансформеры. Они ничего не делают, пока ты не скажешь: «Всё, хватит болтать, давай результат!». Это и есть терминальная операция.

Основные прибамбасы:

  • filter — это твой стражник. «Ты кто такой? Длина имени больше трёх? Проходи. А ты, коротышка, нахуй иди». Чистая проверка.
  • map — это как конвейер на заводе. Берёт каждый элемент, делает с ним что-то одно (например, красит в красный цвет или, как у нас, переводит в верхний регистр) и отправляет дальше. Один на один.
  • flatMap — это уже не конвейер, а хрень посерьёзнее. Берёт элемент, а он, сука, сам как мешок с другими элементами (например, список внутри списка), вытряхивает всё это добро в общий поток и говорит: «Разбирайтесь!». Один ко многим.
  • distinct — эта штука выкидывает повторы. Видит два одинаковых JOHN — один отправляет в утиль. Использует equals(), так что если твои объекты криво его переопределили, то и distinct будет работать как попало.
  • sorted — ну тут всё ясно, сортировщик. Молчаливый такой, стоит, перекладывает элементы по порядку. Может сам, а можешь дать ему инструкцию (компаратор).
  • peek — самый подозрительный тип. Он ничего не меняет, просто подглядывает. Поставил его в цепочку — и он для каждого элемента делает какое-то действие (чаще всего для отладки, чтобы посмотреть, что по конвейеру едет). Главное — не злоупотреблять, а то можно неожиданно всё сломать.
  • limit и skip — близнецы-братья. Один говорит: «Мне только первых N, остальное не надо», а второй: «Первые N — нахуй не сдались, давай что после них».

А теперь смотри, как это всё вместе охуенно работает на живом примере:

List<String> names = Arrays.asList("John", "Alice", "Bob", "Anna", "Alex");

List<String> result = names.stream()               // Запускаем конвейер из списка
        .filter(name -> name.length() > 3)         // Стражник отсеивает Боба (всего 3 буквы, лох)
        .map(String::toUpperCase)                  // Конвейер красит все имена в ЗАГЛАВНЫЕ БУКВЫ
        .sorted()                                  // Молчаливый сортировщик выстраивает по алфавиту
        .distinct()                                // Вышибала на выходе, но дубликатов тут нет
        .collect(Collectors.toList());             // ФИНАЛКА! Всё, стоп машина, упаковываем результат в новый список

System.out.println(result); // Вывод: [ALEX, ALICE, ANNA, JOHN]

Весь этот цирк с цепочкой операций — это просто план работ. Реальная работа (filter, map, sorted) начнётся только в тот момент, когда вызовется терминальная операция — в нашем случае это collect(). До этого можно хоть десять map и filter навесить — потоку будет похуй, он даже не шелохнётся. Ленивые они, блядь, до самого конца.