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

На какие группы делятся методы Stream API

2.0 Middle🔥 171 комментариев
#Stream API и функциональное программирование#Основы Java

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

На какие группы делятся методы Stream API

Методы Stream API разделяются на две основные категории: промежуточные (intermediate) и терминальные (terminal). Это фундаментальное различие определяет логику работы всей цепочки обработки данных.

Структура Stream API

Поток данных
    ↓
[Источник]  →  [Промежуточные операции]*  →  [Терминальная операция]
              
* ноль или более промежуточных операций

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

Характеристики:

  • Возвращают новый Stream
  • Ленивые (lazy evaluation) — не выполняются до терминальной операции
  • Можно цепировать несколько подряд
  • Не потребляют поток

Пример:

Stream<Integer> result = Arrays.stream(new int[]{1, 2, 3, 4, 5})
    .filter(n -> n > 2)     // Промежуточная
    .map(n -> n * 2)        // Промежуточная
    .distinct();            // Промежуточная

// Ничего не выполнилось! Ждём терминальную операцию

Основные промежуточные операции:

1.1 filter() — фильтрация

Arrays.stream("apple banana cherry apricot".split(" "))
    .filter(s -> s.startsWith("a"))  // Оставляем только начинающихся на a
    .forEach(System.out::println);
// apple apricot

1.2 map() — преобразование

Arrays.asList(1, 2, 3, 4, 5).stream()
    .map(n -> n * n)        // Преобразуем в квадраты
    .forEach(System.out::println);
// 1 4 9 16 25

1.3 flatMap() — развёртывание

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

matrix.stream()
    .flatMap(List::stream)  // Развёртываем список в один поток
    .forEach(System.out::println);
// 1 2 3 4 5 6

1.4 distinct() — уникальные значения

Arrays.asList(1, 2, 2, 3, 3, 3, 4).stream()
    .distinct()  // Убираем дубликаты
    .forEach(System.out::println);
// 1 2 3 4

1.5 sorted() — сортировка

Arrays.asList(5, 2, 8, 1, 9).stream()
    .sorted()  // Сортируем по умолчанию
    .forEach(System.out::println);
// 1 2 5 8 9

// С компаратором
Arrays.asList("apple", "fig", "banana", "cherry").stream()
    .sorted(Comparator.comparingInt(String::length))
    .forEach(System.out::println);
// fig apple banana cherry

1.6 limit(n) и skip(n) — срезы

IntStream.rangeClosed(1, 100)
    .skip(10)       // Пропускаем первые 10
    .limit(5)       // Берём только 5 следующих
    .forEach(System.out::println);
// 11 12 13 14 15

1.7 peek() — подсмотр (для отладки)

Arrays.asList(1, 2, 3, 4, 5).stream()
    .filter(n -> n > 2)
    .peek(n -> System.out.println("Прошёл фильтр: " + n))
    .map(n -> n * 10)
    .peek(n -> System.out.println("После map: " + n))
    .collect(Collectors.toList());
// Прошёл фильтр: 3
// После map: 30
// Прошёл фильтр: 4
// После map: 40

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

Характеристики:

  • Не возвращают Stream (возвращают значение или void)
  • Активируют ленивые вычисления (эager evaluation)
  • Завершают работу потока
  • Могут быть использованы только один раз в цепи
  • Вычисляют и возвращают результат

Пример:

long count = Arrays.stream(new int[]{1, 2, 3, 4, 5})
    .filter(n -> n > 2)
    .map(n -> n * 2)
    .count();  // Терминальная операция

System.out.println(count);  // 3
// Теперь всё вычислилось!

2.1 forEach() — обработка каждого элемента

Arrays.asList("Java", "Python", "Go").stream()
    .forEach(System.out::println);
// Java
// Python
// Go

2.2 collect() — сбор результата

// В список
List<Integer> list = Arrays.asList(1, 2, 3, 4).stream()
    .filter(n -> n > 1)
    .collect(Collectors.toList());
// [2, 3, 4]

// В множество
Set<Integer> set = Arrays.asList(1, 1, 2, 2, 3).stream()
    .collect(Collectors.toSet());
// {1, 2, 3}

// В строку
String result = Arrays.asList("a", "b", "c").stream()
    .collect(Collectors.joining(", "));
// "a, b, c"

// В Map
Map<Integer, String> map = Arrays.asList(
    new User(1, "Alice"),
    new User(2, "Bob")
).stream()
    .collect(Collectors.toMap(User::getId, User::getName));
// {1="Alice", 2="Bob"}

2.3 count() — количество элементов

long count = Arrays.asList(1, 2, 3, 4, 5).stream()
    .filter(n -> n % 2 == 0)
    .count();
// 2 (чётные числа: 2, 4)

2.4 min() и max() — минимум и максимум

Optional<Integer> min = Arrays.asList(5, 2, 8, 1, 9).stream()
    .min(Comparator.naturalOrder());
min.ifPresent(System.out::println);  // 1

Optional<Integer> max = Arrays.asList(5, 2, 8, 1, 9).stream()
    .max(Comparator.naturalOrder());
max.ifPresent(System.out::println);  // 9

2.5 anyMatch(), allMatch(), noneMatch() — проверки

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

// anyMatch — есть ли хотя бы один элемент
boolean hasEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0);
// true (есть 2, 4)

// allMatch — все ли элементы
boolean allPositive = numbers.stream()
    .allMatch(n -> n > 0);
// true (все положительные)

// noneMatch — ни один элемент
boolean noNegative = numbers.stream()
    .noneMatch(n -> n < 0);
// true (нет отрицательных)

2.6 findFirst() и findAny() — поиск элемента

Optional<Integer> first = Arrays.asList(1, 2, 3, 4, 5).stream()
    .filter(n -> n > 2)
    .findFirst();  // Первый подходящий
first.ifPresent(System.out::println);  // 3

Optional<Integer> any = Arrays.asList(1, 2, 3, 4, 5).stream()
    .filter(n -> n > 2)
    .findAny();  // Любой подходящий (может быть быстрее в параллельных потоках)
any.ifPresent(System.out::println);  // может быть 3, 4 или 5

2.7 reduce() — свёртка (агрегация)

// Сумма
int sum = Arrays.asList(1, 2, 3, 4, 5).stream()
    .reduce(0, Integer::sum);  // Начальное значение 0
// 15

// Произведение
int product = Arrays.asList(1, 2, 3, 4, 5).stream()
    .reduce(1, (a, b) -> a * b);
// 120

// Конкатенация
Optional<String> concat = Arrays.asList("a", "b", "c").stream()
    .reduce((s1, s2) -> s1 + s2);
concat.ifPresent(System.out::println);  // "abc"

2.8 forEach() и forEachOrdered() — итерация

// Обычный forEach (порядок не гарантирован в параллельных потоках)
Arrays.asList(1, 2, 3, 4, 5).stream()
    .parallel()
    .forEach(System.out::print);
// Может вывести: 3 1 4 5 2

// forEachOrdered (сохраняет порядок)
Arrays.asList(1, 2, 3, 4, 5).stream()
    .parallel()
    .forEachOrdered(System.out::print);
// Выведет: 1 2 3 4 5

3. Специализированные методы для примитивных типов

Для IntStream, LongStream, DoubleStream:

// sum() — сумма
int sum = IntStream.rangeClosed(1, 10).sum();
// 55

// average() — среднее
OptionalDouble avg = IntStream.rangeClosed(1, 10).average();
// 5.5

// summaryStatistics() — полная статистика
IntSummaryStatistics stats = IntStream.rangeClosed(1, 10).summaryStatistics();
System.out.println(stats.getSum());     // 55
System.out.println(stats.getAverage()); // 5.5
System.out.println(stats.getMax());     // 10
System.out.println(stats.getMin());     // 1
System.out.println(stats.getCount());   // 10

Таблица классификации методов

КатегорияВозвращаетЛениваяПримеры
ПромежуточныеStream✅ Даfilter, map, flatMap, sorted, distinct, limit, skip, peek
ТерминальныеЗначение/void❌ НетforEach, collect, count, min, max, anyMatch, findFirst, reduce

Практический пример: Полная цепь

import java.util.stream.Collectors;

public class StreamPipeline {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Сложная цепь
        Map<Boolean, List<Integer>> evenOdd = numbers.stream()
            .filter(n -> n > 2)              // Промежуточная: фильтруем > 2
            .map(n -> n * 2)                 // Промежуточная: умножаем на 2
            .peek(n -> System.out.println("После map: " + n))
            .distinct()                      // Промежуточная: уникальные
            .collect(                        // ТЕРМИНАЛЬНАЯ: собираем результат
                Collectors.partitioningBy(n -> n % 2 == 0)
            );
        
        System.out.println("Чётные: " + evenOdd.get(true));
        System.out.println("Нечётные: " + evenOdd.get(false));
    }
}

Ленивая vs Эагер вычисления

public class LazyEvaluation {
    public static void main(String[] args) {
        System.out.println("=== Ленивые вычисления ===");
        Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5).stream()
            .filter(n -> {
                System.out.println("Filter: " + n);
                return n > 2;
            })
            .map(n -> {
                System.out.println("Map: " + n);
                return n * 2;
            });
        
        System.out.println("Ничего не выполнилось!");
        
        System.out.println("\n=== Активирование терминальной операцией ===");
        stream.forEach(System.out::println);  // Теперь выполнится
    }
}

// Вывод:
// Ничего не выполнилось!
// === Активирование терминальной операцией ===
// Filter: 1
// Filter: 2
// Filter: 3
// Map: 3
// 6
// Filter: 4
// Map: 4
// 8
// Filter: 5
// Map: 5
// 10

Best Practices

Правильно:

  • Используйте filter() перед map() для производительности
  • Используйте collect() вместо forEach() с мутацией переменных
  • Используйте peek() только для отладки
  • Понимайте разницу между промежуточными и терминальными операциями

Неправильно:

  • Не цепируйте слишком много операций (сложно читать)
  • Не забывайте терминальную операцию
  • Не используйте Stream для простых циклов

Вывод

Методы Stream API делятся на две категории:

  1. Промежуточные — возвращают Stream, ленивые, можно цепировать

    • Примеры: filter, map, flatMap, sorted, distinct, limit, skip, peek
  2. Терминальные — возвращают значение/void, активируют вычисления

    • Примеры: forEach, collect, count, min, max, anyMatch, findFirst, reduce

Это разделение — ключ к пониманию работы Stream API и функционального программирования в Java.