Введение
Stream API — это способ обрабатывать последовательности данных (например, списки, массивы) функционально, чисто и читаемо, не изменяя исходные данные.
Что такое Stream?
Stream (поток) — это последовательность элементов, поддерживающая операции для обработки данных.
🔥 Важно: Stream — это не коллекция. Это инструмент для обработки данных из коллекции, массива или другого источника.
Пример: Найти чётные числа и возвести в квадрат
Без Stream (традиционный способ):
С Stream API:
Основные принципы Stream API
- Не изменяет исходные данные
Исходная коллекция остаётся неизменной. - Операции ленивые (lazy)
Большинство операций не выполняются сразу, а только при вызове терминальной операции. - Поддерживает функциональный стиль
Использует лямбда-выражения и метод-ссылки. - Поддерживает параллелизм
Можно легко использовать parallelStream() для ускорения обработки.
Архитектура потока: 3 шага
Каждый Stream проходит три этапа:
- Источник (Source) — откуда берутся данные
- Промежуточные операции (Intermediate operations) — преобразуют поток
- Терминальная операция (Terminal operation) — "запускает" поток и возвращает результат
source.stream()
.intermediateOp1()
.intermediateOp2()
.terminalOp(); // <-- здесь всё и происходит
Шаг 1: Создание потока (Source)
Из коллекции:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Из массива:
String[] arr = {"x", "y", "z"};
Stream<String> stream = Arrays.stream(arr);
Из значений:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Из строки:
IntStream chars = "Hello".chars(); // поток символов как int
Бесконечные потоки (с ленивой оценкой):
Stream<Integer> infinite = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6...
infinite.limit(5).forEach(System.out::println); // ограничим первыми 5
Шаг 2: Промежуточные операции (Intermediate)
Они возвращают новый Stream, позволяя строить цепочки.
Выполняются лениво — только при необходимости.
1. filter(Predicate) — фильтрация
list.stream()
.filter(s -> s.length() > 3)
.forEach(System.out::println);
Оставляет только строки длиной > 3.
2. map(Function) — преобразование
list.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
Переводит все строки в верхний регистр.
3. flatMap(Function) — "разворачивание" потоков
Полезно, когда каждая сущность может давать несколько элементов.
List<String> words = Arrays.asList("hello", "world");
words.stream()
.flatMap(w -> Arrays.stream(w.split(""))) // "hello" → 'h','e','l','l','o'
.forEach(System.out::println);
Выведет все буквы по отдельности.
4. distinct() — убрать дубликаты
Stream.of(1, 2, 2, 3, 3, 3)
.distinct()
.forEach(System.out::println); // 1, 2, 3
5. sorted() — сортировка
list.stream()
.sorted()
.forEach(System.out::println); // по алфавиту
// или с компаратором
.sorted((a, b) -> b.compareTo(a)) // по убыванию
6. limit(n) и skip(n) — ограничение
Stream.of(1, 2, 3, 4, 5)
.skip(2) // пропустить первые 2 → 3,4,5
.limit(2) // взять только 2 → 3,4
.forEach(System.out::println);
Шаг 3: Терминальные операции (Terminal)
Они запускают выполнение потока и возвращают результат (не Stream!).
1. forEach(Consumer) — выполнить действие
stream.forEach(System.out::println);
2. collect(Collector) — собрать в коллекцию
Самый частый способ завершить поток.
List<String> result = stream.collect(Collectors.toList());
Set<Integer> unique = stream.collect(Collectors.toSet());
String joined = stream.collect(Collectors.joining(", "));
💡 Collectors — мощный вспомогательный класс.
3. count() — посчитать элементы
long count = stream.filter(x -> x > 10).count();
4. anyMatch, allMatch, noneMatch — проверка условий
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean noneNull = list.stream().noneMatch(Objects::isNull);
5. findFirst(), findAny() — найти элемент
Optional<String> firstLong = list.stream()
.filter(s -> s.length() > 5)
.findFirst();
if (firstLong.isPresent()) {
System.out.println("Нашли: " + firstLong.get());
}
6. reduce() — свёртка (агрегация)
Объединяет элементы в одно значение.
// Сумма чисел
int sum = Stream.of(1, 2, 3, 4).reduce(0, (a, b) -> a + b);
// Максимум
Optional<Integer> max = Stream.of(1, 5, 3).reduce(Integer::max);
Пример: Полный цикл обработки
Задача: У нас есть список пользователей. Нужно:
- Найти активных пользователей
- Отфильтровать тех, у кого возраст > 18
- Взять первых 3
- Преобразовать в имена в верхнем регистре
- Собрать в список
Заключение
Stream API — это революция в обработке данных в Java. Он позволяет:
- Писать чистый и читаемый код
- Избавиться от "грязных" циклов и временных списков
- Легко комбинировать операции
Примеры, рассмотренные в статье, можно по ссылке:
https://github.com/ShkrylAndrei/blog_yandex/tree/main/src/main/java/info/shkryl/useStream