Ответ
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()); // Терминальная — собрали в новый список, всё, труба.
Вот и вся наука. Цепляешь операции, как вагоны, а в конце — бац — терминальная, и поезд приехал. Главное — не забыть её вызвать, а то так и будешь смотреть на пустой поток и думать, что же пошло не так.