Что такое Stream API в Java?

Ответ

Stream API — это абстракция для декларативной обработки последовательностей данных (коллекций, массивов, I/O каналов) в функциональном стиле, добавленная в Java 8.

Ключевые концепции:

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

Типы операций:

  1. Промежуточные (Intermediate): Возвращают новый Stream (ленивые).
    • filter(Predicate<T>) – фильтрация по условию.
    • map(Function<T, R>) – преобразование каждого элемента.
    • sorted() – сортировка.
    • distinct() – удаление дубликатов.
  2. Терминальные (Terminal): Запускают выполнение конвейера и возвращают результат (не Stream).
    • collect(Collector) – агрегация в коллекцию или строку.
    • forEach(Consumer) – выполнение действия для каждого элемента.
    • reduce() – свертка элементов в одно значение.
    • count(), anyMatch(), allMatch() – агрегатные проверки.

Пример:

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

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

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

Параллельные потоки: Для обработки больших данных можно использовать parallelStream() для автоматического распараллеливания операций по ядрам процессора.

Ответ 18+ 🔞

А, Stream API, говоришь? Ну это ж та самая штука, которая в Java 8 приехала и всех так охуевать заставила. Типа, раньше мы коллекции в циклах крутили, как обезьяны, а теперь можно красиво, декларативно, почти как на модных языках. В общем, мартышлюшка стала чуть менее волосатой.

Смотри, в чём суть, чтобы не пиздеть просто так. Это абстракция, такая надстройка, чтобы данные обрабатывать — коллекции там, массивы — но не тыкая в них палкой-итератором, а описывая что сделать, а не как. Красота, блядь.

Основные пиздели, которые надо в башке держать:

  • Источник не трогает! Это святое. Stream работает с данными, но исходную коллекцию он не портит. Сделал что-то — получил новый поток или результат. Исходник остался цел и девственен, как снег.
  • Ленивый, как мой кот сутра. Все промежуточные операции (их ещё intermediate зовут) нихуя не делают, пока ты не позовёшь операцию терминальную. Они просто в цепочку выстраиваются и ждут команды «поехали». Экономия, блядь, ресурсов — зачем делать, если результат не нужен?
  • Конвейер, ёпта. Всё строится, как на заводе: взял источник -> навешал кучу операций-фильтров-преобразований -> в конце получил готовый продукт. Элегантно, сука.
  • Одноразовый, как шприц. Вызвал терминальную операцию — поток схлопнулся. Хочешь ещё раз пробежаться — создавай новый. Нельзя повторно использовать, иначе получишь IllegalStateException — вот тебе и вся магия нахуй.

Теперь про операции, их два лагеря:

  1. Промежуточные (Лентяи). Возвращают новый Stream, чтоб цепочку продолжать.

    • filter(Predicate<T>) — отсеивает по условию. Нужно только тех, кто условие проходит. Остальных — нахуй.
    • map(Function<T, R>) — превращает каждый элемент из одного в другое. Был String, стал Integer. Фокус-покус.
    • sorted() — сортирует. Думал, всё просто? А если компаратор свой? Вот тут-то и начинается веселье.
    • distinct() — убирает повторы. Оставляет только уникальных пассажиров.
  2. Терминальные (Работяги). Запускают весь этот ленивый конвейер и выдают финальный результат. После них — пиздец потоку.

    • collect(Collector) — собирает всё в кучу: в список, в сет, в мапу, в строку. Самый частый гость.
    • forEach(Consumer) — делает что-то с каждым элементом. Для сайд-эффектов, типа вывода в консоль.
    • reduce() — сводит всю последовательность к одному значению. Сумма, максимум, конкатенация — всё тут.
    • count(), anyMatch(), allMatch() — проверки на вшивость. «Хотя бы один?», «Все ли?», «Скво?».

Ну и пример, чтобы не быть пиздаболом:

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

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

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

Видишь? Ни одного for, ни одного итератора. Чисто описание что нужно: взять, отфильтровать, намапить, отсортировать, собрать. Красота, блядь.

А, да, забыл про пушку — параллельность. Если список овердохуища большой, можно вместо stream() вызвать parallelStream(). И тогда обработка размажется по ядрам процессора сама собой. Но это уже тема для отдельного разговора, потому что там свои грабли: синхронизация, производительность, которая может и просесть, если не угадать. В общем, волнение ебать, но штука мощная.