Какие основные методы Stream API в Java?

Ответ

Stream API предоставляет два типа операций: промежуточные (возвращают новый Stream) и терминальные (завершают поток и возвращают результат).

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

  • filter(Predicate<T>) – отбирает элементы, удовлетворяющие условию.
  • map(Function<T, R>) – преобразует каждый элемент.
  • flatMap(Function<T, Stream<R>>) – преобразует элемент в поток и объединяет все потоки в один.
  • distinct() – удаляет дубликаты (использует equals()).
  • sorted() / sorted(Comparator<T>) – сортирует элементы.
  • peek(Consumer<T>) – выполняет действие для каждого элемента (часто для отладки).
  • limit(long n) – ограничивает поток первыми n элементами.
  • skip(long n) – пропускает первые n элементов.

Терминальные операции:

  • forEach(Consumer<T>) – выполняет действие для каждого элемента.
  • collect(Collector<T, A, R>) – собирает элементы в коллекцию или другую структуру.
  • reduce(BinaryOperator<T>) – выполняет свертку элементов (например, нахождение суммы).
  • count() – возвращает количество элементов.
  • min(Comparator<T>) / max(Comparator<T>) – находит минимальный/максимальный элемент.
  • anyMatch(Predicate<T>) / allMatch() / noneMatch() – проверяют, удовлетворяет ли поток условию.
  • findFirst() / findAny() – возвращают опциональный элемент потока.

Пример использования:

List<String> result = names.stream()
    .filter(name -> name.length() > 3)  // Промежуточная
    .map(String::toUpperCase)           // Промежуточная
    .sorted()                           // Промежуточная
    .collect(Collectors.toList());      // Терминальная

Почему это важно: Промежуточные операции «ленивы» – вычисления происходят только при вызове терминальной операции, что оптимизирует обработку данных.

Ответ 18+ 🔞

Давай разберём эту вашу Stream API, а то народ путается, как слепой кот в сортире. Представь, что у тебя есть поток данных — ну, как водопровод, только с данными. Так вот, в этом потоке можно делать две хуйни: либо что-то с ним делать по дороге, либо в конце концов получить результат и на этом всё, пиздец.

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

Смотри, какие бывают:

  • filter(Predicate<T>) — отсеивает всякий шлак. Оставляет только то, что подходит под условие. Как решето, ёпта.
  • map(Function<T, R>) — превращает каждый элемент во что-то другое. Был Integer, стал String — магия, блядь, чистая магия.
  • flatMap(Function<T, Stream<R>>) — а это для умников. Берёт элемент, делает из него целый новый поток, а потом все эти потоки склеивает в один. Как если бы ты развернул вложенные списки в один большой.
  • distinct() — убирает повторы. Работает через equals(), так что если твои объекты кривые, нихуя не получится.
  • sorted() / sorted(Comparator<T>) — сортирует. Без компаратора — как получится, с компаратором — как ты скажешь.
  • peek(Consumer<T>) — подглядыватель. Выполняет действие для каждого элемента, но не меняет поток. Чисто для отладки, чтоб посмотреть, что там внутри творится, пока всё не накрылось медным тазом.
  • limit(long n) — берёт только первые n элементов. Остальное — нахуй.
  • skip(long n) — наоборот, первые n штук пропускает. Потом уже работает.

А теперь терминальные операции — это конец, финиш, приехали. После них поток закрывается, и ты получаешь какой-то конкретный результат или просто действие выполняется.

Вот они:

  • forEach(Consumer<T>) — делает что-то с каждым элементом, и всё. Результата нет, только side-effect.
  • collect(Collector<T, A, R>) — вот это мощь! Собирает всё в кучу: в список, в мапу, в строку — во что угодно. Самый часто используемый, пиздец как удобно.
  • reduce(BinaryOperator<T>) — свёртка. Берет элементы и combines them into a single result. Например, сумму всех чисел найти.
  • count() — просто считает, сколько там всего элементов. Ни хуя себе, да?
  • min(Comparator<T>) / max(Comparator<T>) — находит самого маленького или самого большого по твоим правилам.
  • anyMatch(Predicate<T>) / allMatch() / noneMatch() — проверяют, есть ли хоть один подходящий, все ли подходят, или ни один не подходит. Возвращают boolean.
  • findFirst() / findAny() — находят элемент. Возвращают Optional, потому что может и не найтись нихуя.

А теперь смотри, в чём прикол-то: все эти промежуточные операции — они ленивые, сука! Пока ты не вызвал терминальную операцию, нихуя не происходит. Данные не фильтруются, не мапятся — ничего. Всё ждёт своего часа. Это называется отложенное выполнение, и это овердохуища экономит ресурсы.

Ну и пример, чтоб вообще всё встало на свои места:

List<String> result = names.stream()
    .filter(name -> name.length() > 3)  // Промежуточная — отфильтровали короткие имена
    .map(String::toUpperCase)           // Промежуточная — перевели в верхний регистр
    .sorted()                           // Промежуточная — отсортировали по алфавиту
    .collect(Collectors.toList());      // Терминальная — собрали в новый список, всё, труба.

Вот и вся наука. Цепляешь операции, как вагоны, а в конце — бац — терминальная, и поезд приехал. Главное — не забыть её вызвать, а то так и будешь смотреть на пустой поток и думать, что же пошло не так.