Ответ
Stream API (пакет java.util.stream) — это абстракция для декларативной и функциональной обработки последовательностей данных (коллекций, массивов, I/O каналов), представленная в Java 8.
Ключевые концепции:
- Не хранит данные. Stream — это конвейер операций над источником данных.
- Не изменяет источник. Все операции возвращают новый Stream, оставляя исходную коллекцию неизменной.
- Ленивые (lazy) вычисления. Промежуточные операции (
filter,map) выполняются только при вызове терминальной операции. - Однократная трассировка. Stream нельзя использовать повторно после вызова терминальной операции.
Типы операций:
- Промежуточные (Intermediate): Возвращают
Stream<T>.filter(Predicate<T>)— фильтрация.map(Function<T, R>)— преобразование.sorted(),distinct(),limit(n),skip(n).
- Терминальные (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 — выкинул. Хочешь ещё раз пройтись по данным? Создавай новый, пидорас. Он как сигарета — один раз поджёг и всё.
Что там можно делать? Две группы операций:
-
Промежуточные (Intermediate): Это те, после которых можно ещё что-то цеплять. Они возвращают новый Stream.
filter(...)— отсеиваем всё, что не подходит под условие. Типа, "оставить только тех, кто не проебал дедлайн".map(...)— превращаем каждый элемент во что-то другое. "Взять каждого работягу и вытащить из него только зарплату".sorted(),distinct(),limit(n)— ну, тут понятно, сортировка, убрать повторы, взять первые N штук.
-
Терминальные (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, когда ничего не найдено. Всегда проверяй, есть ли там значение, прежде чем лезть в него руками.