Найти в Дзене
Записки о Java

Введение в Stream API в Java

Stream API — это способ обрабатывать последовательности данных (например, списки, массивы) функционально, чисто и читаемо, не изменяя исходные данные. Stream (поток) — это последовательность элементов, поддерживающая операции для обработки данных. 🔥 Важно: Stream — это не коллекция. Это инструмент для обработки данных из коллекции, массива или другого источника. Без Stream (традиционный способ): С Stream API: Каждый Stream проходит три этапа: source.stream() .intermediateOp1() .intermediateOp2() .terminalOp(); // <-- здесь всё и происходит 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 Они возвращают новый Stream, п
Оглавление
Рисунок: Stream API в JAVA
Рисунок: Stream API в JAVA

Введение

Stream API — это способ обрабатывать последовательности данных (например, списки, массивы) функционально, чисто и читаемо, не изменяя исходные данные.

Что такое Stream?

Stream (поток) — это последовательность элементов, поддерживающая операции для обработки данных.

🔥 Важно: Stream — это не коллекция. Это инструмент для обработки данных из коллекции, массива или другого источника.

Пример: Найти чётные числа и возвести в квадрат

Без Stream (традиционный способ):

Рисунок: листинг поиска четных чисел и возведения их в квадрат стандартными способом
Рисунок: листинг поиска четных чисел и возведения их в квадрат стандартными способом

С Stream API:

Рисунок: листинг поиска четных чисел и возведения их в квадрат с использованием Stream API
Рисунок: листинг поиска четных чисел и возведения их в квадрат с использованием Stream API

Основные принципы Stream API

  1. Не изменяет исходные данные
    Исходная коллекция остаётся
    неизменной.
  2. Операции ленивые (lazy)
    Большинство операций
    не выполняются сразу, а только при вызове терминальной операции.
  3. Поддерживает функциональный стиль
    Использует лямбда-выражения и метод-ссылки.
  4. Поддерживает параллелизм
    Можно легко использовать parallelStream() для ускорения обработки.

Архитектура потока: 3 шага

Каждый Stream проходит три этапа:

  1. Источник (Source) — откуда берутся данные
  2. Промежуточные операции (Intermediate operations) — преобразуют поток
  3. Терминальная операция (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
  • Преобразовать в имена в верхнем регистре
  • Собрать в список
Рисунок: листинг класса User
Рисунок: листинг класса User
Рисунок: пример использования Stream API на классе User
Рисунок: пример использования Stream API на классе User

Заключение

Stream API — это революция в обработке данных в Java. Он позволяет:

  • Писать чистый и читаемый код
  • Избавиться от "грязных" циклов и временных списков
  • Легко комбинировать операции

Примеры, рассмотренные в статье, можно по ссылке:

https://github.com/ShkrylAndrei/blog_yandex/tree/main/src/main/java/info/shkryl/useStream