Из каких основных частей состоит Stream API в Java?

Ответ

Поток (Stream) в Java состоит из трех логических частей, образующих конвейер обработки:

1. Источник (Source) Создает поток из данных. Источники бывают:

  • Коллекции: collection.stream()
  • Массивы: Arrays.stream(array)
  • Значения: Stream.of("a", "b", "c")
  • Файлы: Files.lines(path)
  • Генераторы: Stream.iterate(), Stream.generate()

2. Промежуточные операции (Intermediate Operations) Преобразуют элементы потока, возвращая новый поток. Ленивые — выполняются только при вызове терминальной операции.

  • filter(Predicate<T>) — фильтрация.
  • map(Function<T, R>) — преобразование.
  • sorted() — сортировка.
  • distinct() — удаление дубликатов.
  • limit(long n) — ограничение количества элементов.

3. Терминальная операция (Terminal Operation) Завершает поток, запускает выполнение всего конвейера и производит результат или побочный эффект. Энергичная.

  • collect(Collector) — агрегация в коллекцию.
  • forEach(Consumer) — выполнение действия для каждого элемента.
  • reduce() — свертка к одному значению.
  • count(), anyMatch(), findFirst() — получение результата.

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

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Anna");
List<String> result = names.stream()          // Источник
        .filter(name -> name.startsWith("A")) // Промежуточная операция
        .map(String::toUpperCase)             // Промежуточная операция
        .collect(Collectors.toList());        // Терминальная операция
// result = ["ALICE", "ANNA"]

Ответ 18+ 🔞

Смотри, чтобы не сойти с ума от этих ваших стримов, давай разложу по полочкам, как это всё работает, без этой охуевшей академической зауми.

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

1. Источник (Source) — откуда данные берутся, ёпта. Это начало всего, точка старта. Как будто ты подъехал к складу и начал грузить сырьё на ленту. Вариантов дохуя:

  • Из коллекции: список.stream() — элементарно, Ватсон.
  • Из массива: Arrays.stream(массив) — тоже понятно.
  • Из конкретных значений: Stream.of("раз", "два", "три") — вот тебе готовый набор.
  • Из файла: Files.lines(путь_к_файлу) — читаем построчно, красота.
  • Из генератора: Stream.iterate() или Stream.generate() — когда надо нагенерировать данных овердохуища, хоть до второго пришествия.

2. Промежуточные операции (Intermediate Operations) — это где мы с данными хуйню делаем. Вот тут вся магия и происходит. Ты ставишь на конвейер станки, которые каждый элемент обрабатывают. ВАЖНОЕ ЗАМЕЧАНИЕ, БЛЯДЬ! Эти операции — ленивые, как мой кот по утрам. Они нихуя не делают, пока ты не скажешь «работай!». Просто готовят план работ.

  • filter(условие) — отсеивает всё, что не подходит. Как решето, ёбта.
  • map(функция_преобразования) — превращает один элемент в другой. Был Integer, стал String — волшебство, но на практике.
  • sorted() — сортирует. Внезапно, да?
  • distinct() — выкидывает повторы. Один и тот же элемент дважды не проскочит.
  • limit(n) — говорит: «Хватит, браток, мне только первых N штук».

3. Терминальная операция (Terminal Operation) — точка невозврата, где всё заканчивается. Вот это та самая команда «работай!», которая пинает весь этот ленивый конвейер в жопу. После неё поток закрыт, с ним больше ничего сделать нельзя. Она энергичная — сразу всех заставляет шевелиться.

  • collect(сборщик) — самый частый гость. Собирает результат в коллекцию (List, Set, Map), строку или ещё куда.
  • forEach(действие) — делает что-то с каждым элементом (например, печатает). Результата не возвращает, только побочный эффект.
  • reduce() — сводит весь поток к одному-единственному значению (сумма, максимум).
  • count(), anyMatch(), findFirst() — быстрые проверки: посчитать, есть ли хоть один подходящий, найти первый.

А теперь смотри, как это в жизни выглядит, на простом примере:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Anna");
List<String> result = names.stream()          // 1. Источник: грузим имена на конвейер
        .filter(name -> name.startsWith("A")) // 2. Промежуточная: фильтр — только на "A"
        .map(String::toUpperCase)             // 2. Промежуточная: маппер — делаем большими буквами
        .collect(Collectors.toList());        // 3. Терминальная: собираем в список — ВСЁ, ПРОЦЕСС ПОШЁЛ!
// В result теперь лежит ["ALICE", "ANNA"]

Короче, алгоритм простой, блядь: Создал поток -> Настроил обработку -> Запустил и получил результат. Главное — не пытаться использовать поток после терминальной операции, а то получишь IllegalStateException и будешь сидеть, чесать репу.