← Назад к вопросам

Как использовать Stream API?

2.0 Middle🔥 172 комментариев
#Java

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как использовать Stream API в Java

Stream API — это мощный функциональный подход к обработке коллекций и данных, появившийся в Java 8. Он позволяет выполнять сложные операции обработки данных в декларативном стиле, часто с параллелизацией.

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

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

  1. Создание Stream (источник)
  2. Промежуточные операции (конвейерные операции, возвращающие новый Stream)
  3. Терминальная операция (запускает обработку и возвращает результат)

1. Создание Stream (Источники)

Stream можно создать из различных источников:

// Из коллекции
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> streamFromList = list.stream();

// Из массива
Stream<String> streamFromArray = Arrays.stream(new String[]{"a", "b", "c"});

// Из значений
Stream<String> streamOfValues = Stream.of("a", "b", "c");

// Бесконечные Stream
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
Stream<Double> randomStream = Stream.generate(Math::random);

// Из примитивов
IntStream intStream = IntStream.range(1, 10); // 1..9

2. Промежуточные операции (Intermediate Operations)

Эти операции "ленивые" — они выполняются только при вызове терминальной операции.

List<String> names = Arrays.asList("John", "Alice", "Bob", "Anna", "Alex");

// Фильтрация
Stream<String> filtered = names.stream()
    .filter(name -> name.startsWith("A")); // Alice, Anna, Alex

// Преобразование (map)
Stream<Integer> lengths = names.stream()
    .map(String::length); // 4, 5, 3, 4, 4

// Сортировка
Stream<String> sorted = names.stream()
    .sorted(); // Alice, Alex, Anna, Bob, John

// Уникальные значения
Stream<String> distinct = Stream.of("a", "b", "a", "c")
    .distinct(); // a, b, c

// Пропуск и ограничение
Stream<String> limited = names.stream()
    .skip(2)    // пропустить первые 2
    .limit(3);  // взять не более 3

3. Терминальные операции (Terminal Operations)

Запускают выполнение конвейера и возвращают результат:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Сбор в коллекцию
List<Integer> collected = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList()); // [2, 4]

// Поиск элементов
boolean anyMatch = numbers.stream().anyMatch(n -> n > 3); // true
boolean allMatch = numbers.stream().allMatch(n -> n > 0); // true
Optional<Integer> first = numbers.stream().findFirst(); // Optional[1]

// Агрегатные операции
Optional<Integer> max = numbers.stream().max(Integer::compare); // Optional[5]
Optional<Integer> min = numbers.stream().min(Integer::compare); // Optional[1]
long count = numbers.stream().count(); // 5

// Редукция (сведение)
Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b); // Optional[15]
Integer sum2 = numbers.stream()
    .reduce(0, Integer::sum); // 15

// Итерация
numbers.stream().forEach(System.out::println);

4. Продвинутые примеры использования

// Группировка
List<Person> people = Arrays.asList(
    new Person("John", "London"),
    new Person("Alice", "Paris"),
    new Person("Bob", "London")
);

Map<String, List<Person>> byCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity));

// Параллельная обработка
long count = people.parallelStream()
    .filter(p -> p.getCity().equals("London"))
    .count();

// Цепочка операций
List<String> result = people.stream()
    .filter(p -> p.getAge() > 18)
    .map(Person::getName)
    .sorted()
    .collect(Collectors.toList());

// Работа с примитивами
double average = IntStream.range(1, 100)
    .filter(n -> n % 2 == 0)
    .average()
    .orElse(0.0);

5. Важные особенности Stream API

  • Ленивость: Промежуточные операции не выполняются до вызова терминальной операции
  • Однократное использование: Stream нельзя использовать повторно после терминальной операции
  • Невозможность изменения источника: Stream не модифицирует исходную коллекцию
  • Оптимизация: Некоторые операции (например, limit() с sorted()) оптимизированы
  • Порядок выполнения: Для последовательных Stream порядок сохраняется, для параллельных — может нарушаться

6. Практические рекомендации

  • Используйте method references (String::length) вместо лямбда-выражений, где это уместно
  • Для примитивных типов используйте специализированные Stream (IntStream, LongStream, DoubleStream)
  • Параллельные Stream (parallelStream()) эффективны только для больших объемов данных
  • Избегайте побочных эффектов в лямбда-выражениях
  • Используйте Optional для безопасной работы с потенциально отсутствующими значениями

Stream API значительно упрощает обработку данных, делает код более читаемым и поддерживаемым, а также предоставляет возможности для параллельной обработки "из коробки". Однако важно понимать его внутреннее устройство, чтобы избежать распространенных ошибок и неэффективного использования памяти.

Как использовать Stream API? | PrepBro