На какие группы делятся методы Stream API
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
На какие группы делятся методы 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 делятся на две категории:
-
Промежуточные — возвращают Stream, ленивые, можно цепировать
- Примеры: filter, map, flatMap, sorted, distinct, limit, skip, peek
-
Терминальные — возвращают значение/void, активируют вычисления
- Примеры: forEach, collect, count, min, max, anyMatch, findFirst, reduce
Это разделение — ключ к пониманию работы Stream API и функционального программирования в Java.