Что такое Stream API в Java и каковы его основные особенности?

Ответ

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

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

  • Не хранит данные. Stream — это конвейер операций над источником данных.
  • Не изменяет источник. Все операции возвращают новый Stream, оставляя исходную коллекцию неизменной.
  • Ленивые (lazy) вычисления. Промежуточные операции (filter, map) выполняются только при вызове терминальной операции.
  • Однократная трассировка. Stream нельзя использовать повторно после вызова терминальной операции.

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

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

Примеры:

// 1. Фильтрация и преобразование
List<String> names = List.of("Anna", "Bob", "Alex", "Alice");
List<String> result = names.stream()
        .filter(name -> name.startsWith("A")) // Промежуточная
        .map(String::toUpperCase)             // Промежуточная
        .sorted()
        .collect(Collectors.toList());        // Терминальная
// result = [ALEX, ALICE, ANNA]

// 2. Работа с примитивами (IntStream, LongStream)
int sum = IntStream.range(1, 10) // 1..9
        .filter(x -> x % 2 == 0)
        .sum(); // sum = 20

// 3. Параллельная обработка
long count = largeList.parallelStream() // Использует ForkJoinPool
        .filter(item -> item.isValid())
        .count();

Важные особенности:

  • Short-circuit операции: limit(), findFirst(), anyMatch() могут завершить обработку досрочно.
  • Порядок в parallel stream: Не гарантируется, если только не используется forEachOrdered().
  • Использование с Optional: Многие терминальные операции возвращают Optional (например, findFirst()).

Ответ 18+ 🔞

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

Суть, если на пальцах:

  • Данные не хранит. Это не коллекция, куда ты что-то сложил. Это, сука, конвейер, инструкция, что сделать с данными, когда ты скажешь "поехали".
  • Исходник не трогает. Все операции — они как бы в воздухе висят, пока ты не решишь собрать результат. Исходный список остаётся святым и неприкосновенным, как твоя последняя бутылка пива в холодильнике.
  • Ленивая, как мой кот. Пока ты не крикнешь терминальную операцию, типа collect или count, ничего по-настоящему и не произойдёт. Все эти filter и map просто строят план, блядь, а не выполняют его.
  • Одноразовая. Использовал Stream — выкинул. Хочешь ещё раз пройтись по данным? Создавай новый, пидорас. Он как сигарета — один раз поджёг и всё.

Что там можно делать? Две группы операций:

  1. Промежуточные (Intermediate): Это те, после которых можно ещё что-то цеплять. Они возвращают новый Stream.

    • filter(...) — отсеиваем всё, что не подходит под условие. Типа, "оставить только тех, кто не проебал дедлайн".
    • map(...) — превращаем каждый элемент во что-то другое. "Взять каждого работягу и вытащить из него только зарплату".
    • sorted(), distinct(), limit(n) — ну, тут понятно, сортировка, убрать повторы, взять первые N штук.
  2. Терминальные (Terminal): Это финиш, пиздец, точка. После них поток закрыт. Они запускают всю эту ленивую хуйню в работу.

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

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

// Допустим, есть у нас список имён, обычный такой
List<String> names = List.of("Anna", "Bob", "Alex", "Alice");

// Хотим имена на 'A', в верхнем регистре, отсортированные
List<String> result = names.stream()               // Запускаем поток
        .filter(name -> name.startsWith("A"))     // Отфильтровали Анну, Алекса и Алису. Боба выкинули нахуй
        .map(String::toUpperCase)                 // Перевели в заглавные. Теперь они орут
        .sorted()                                 // Отсортировали по алфавиту, потому что почему бы и нет
        .collect(Collectors.toList());            // ВСЁ, собрали в новый список. Исходный names не тронут!
// result = [ALEX, ALICE, ANNA]

// Работа с числами (IntStream) — тут вообще красота
int sum = IntStream.range(1, 10) // Цифры от 1 до 9
        .filter(x -> x % 2 == 0) // Оставили только чётные (2, 4, 6, 8)
        .sum();                  // Сложили их. sum = 20. Красота же!

// Если данных овердохуища, можно распараллелить
long count = largeList.parallelStream() // Включил многопоточность, пусть ядра греются
        .filter(item -> item.isValid()) // Каждый поток фильтрует свой кусок
        .count();                       // Считаем, сколько всего валидного нашлось

Важные фишки, которые надо помнить, чтобы не обосраться:

  • Short-circuit операции: Такие штуки как limit(5) или findFirst() могут остановить обработку досрочно. Нашли пять — всё, пошли домой. Экономия, блядь.
  • Порядок в параллельном потоке: Если используешь parallelStream(), порядок элементов при операциях вроде forEach не гарантирован. Хочешь порядок — используй forEachOrdered(), но будь готов к потенциальным тормозам.
  • Optional — наш друг-защитник: Многие терминальные операции (findFirst(), max()) возвращают Optional. Это чтобы не ловить потом NullPointerException, когда ничего не найдено. Всегда проверяй, есть ли там значение, прежде чем лезть в него руками.