Stream API – мощный инструмент для обработки данных. Его основные принципы:
✅ Композиция – построение логики из небольших, независимых и чистых функций.
✅ Single Responsibility – каждый шаг выполняет одну конкретную операцию.
Такой подход обеспечивает:
✔️ Читаемость кода.
✔️ Отсутствие локальных переменных.
✔️ Линейную последовательность действий.
Основные компоненты Stream API
Любой поток состоит из трех ключевых частей:
1️⃣ Источник данных (коллекция, массив, файл и т. д.).
2️⃣ Промежуточные преобразования (filter, map и другие).
3️⃣ Конечная операция (коллекционирование, агрегация, перебор).
Способы создания стрима
Stream можно создать несколькими способами:
🔹 Из коллекции:
collection.stream() 🔹 Из массива:
Arrays.stream(array) 🔹 Из набора элементов:
Stream.of(1, 2, 3) 🔹 Бесконечный поток:
Stream.iterate(0, n -> n + 1) 🔹 Бесконечный поток с ограничением (Java 9):
Stream.iterate(1, n -> n < 100, n -> n * 2) 🔹 Генерация элементов:
Stream.generate(Math::random) 🔹 Диапазон значений:
-
IntStream.range(1, 5) (1, 2, 3, 4) -
IntStream.rangeClosed(1, 5) (1, 2, 3, 4, 5) 🔹 Из файла:
Files.lines(Path.of("file.txt")) 🔹 Из строки:
"abc".chars() Промежуточные операции
Stream API поддерживает множество преобразований. Наиболее распространенные:
✔️ Фильтрация:
filter(Predicate<T>) – оставляет только элементы, соответствующие условию. ✔️ Удаление дубликатов:
distinct() – исключает повторяющиеся элементы. ✔️ Ограничение количества:
limit(n) – берет первые n элементов. ✔️ Сортировка:
sorted() – упорядочивает элементы. Менее очевидные операции
🔹 map(Function<T, R>) – применяет функцию к каждому элементу.
🔹 flatMap(Function<T, Stream<R>>) – «разворачивает» элементы из вложенных структур.
🔹 takeWhile(Predicate<T>) (Java 9) – берет элементы, пока выполняется условие.
🔹 dropWhile(Predicate<T>) (Java 9) – пропускает элементы, пока условие выполняется.
🔹 peek(Consumer<T>) – выполняет действие без изменения элементов (удобно для логирования).
Конечные операции
Стрим начинает обработку данных только при вызове конечной операции:
📌 Коллекционирование:
-
collect(Collectors.toList()) – собирает в List. -
collect(Collectors.toSet()) – собирает в Set. 📌 Поиск элементов:
-
findFirst() – первый элемент. -
findAny() – любой элемент (оптимизирован для параллельных потоков). -
anyMatch(Predicate<T>) – хотя бы один элемент удовлетворяет условию. -
allMatch(Predicate<T>) – все элементы удовлетворяют условию. -
noneMatch(Predicate<T>) – ни один элемент не удовлетворяет условию. 📌 Агрегация:
-
min(Comparator<T>) – минимальный элемент. -
max(Comparator<T>) – максимальный элемент. -
count() – количество элементов. -
reduce(BinaryOperator<T>) – свертка элементов в одно значение. 📌 Побочные эффекты:
-
forEach(Consumer<T>) – выполняет действие над каждым элементом. -
forEachOrdered(Consumer<T>) – выполняет действие, сохраняя порядок (важно для параллельных потоков). Особенности работы со Stream API
1️⃣ Стрим – это не структура данных
Он лишь обходит источник, выполняя операции лениво.
2️⃣ Стрим нельзя использовать повторно
После вызова конечной операции повторное использование потока приведет к
IllegalStateException. 3️⃣ Исходные данные не изменяются
Методы Stream API не модифицируют исходную коллекцию.
Разбор сложных случаев
❌ Ошибочный код:
Stream.of(-1, 0, 1).max(Math::max).get();
✅ Почему ошибка?
Метод
max() принимает Comparator<T>, но Math.max(a, b) – это BiFunction<Integer, Integer, Integer>. Они не эквивалентны! ℹ️ Решение:
Stream.of(-1, 0, 1).max(Integer::compareTo).get(); // Вернет 1
📲 Мы в MAX
👉@BookJava