В чём разница между терминальными и нетерминальными (промежуточными) операциями в Stream API?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Терминальные и нетерминальные операции в Stream API
Stream API в Java делит операции на две категории. Понимание разницы критично для использования потоков данных эффективно.
Нетерминальные (промежуточные) операции
Нетерминальные операции преобразуют один поток в другой, возвращая новый Stream. Они ленивы — не выполняются до тех пор, пока не будет вызвана терминальная операция.
Основные нетерминальные операции:
1. map() — преобразование элементов
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squared = numbers.stream()
.map(n -> n * n) // Преобразование 1→1, 2→4, 3→9...
.collect(Collectors.toList()); // Терминальная операция
System.out.println(squared); // [1, 4, 9, 16, 25]
2. filter() — фильтрация элементов
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Anna");
List<String> aNames = names.stream()
.filter(name -> name.startsWith("A")) // Оставляем только начинающиеся на "A"
.collect(Collectors.toList());
System.out.println(aNames); // [Alice, Anna]
3. flatMap() — развёртывание потоков
List<List<Integer>> lists = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flattened = lists.stream()
.flatMap(list -> list.stream()) // Превращаем List<List<>> в Stream<>
.collect(Collectors.toList());
System.out.println(flattened); // [1, 2, 3, 4, 5, 6]
4. distinct() — удаление дубликатов
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> unique = numbers.stream()
.distinct() // Оставляем только уникальные
.collect(Collectors.toList());
System.out.println(unique); // [1, 2, 3, 4]
5. sorted() — сортировка
List<String> words = Arrays.asList("zebra", "apple", "banana");
List<String> sorted = words.stream()
.sorted() // Сортирует в естественном порядке
.collect(Collectors.toList());
System.out.println(sorted); // [apple, banana, zebra]
// С компаратором
List<String> reverseSorted = words.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(reverseSorted); // [zebra, banana, apple]
6. limit() и skip() — ограничение и пропуск
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Первые 3 элемента
List<Integer> first3 = numbers.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(first3); // [1, 2, 3]
// Пропустить первые 2, взять 3
List<Integer> page = numbers.stream()
.skip(2) // Пропускаем первые 2
.limit(3) // Берём следующие 3
.collect(Collectors.toList());
System.out.println(page); // [3, 4, 5]
7. peek() — отладка
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Integer> result = numbers.stream()
.map(n -> n * 2)
.peek(n -> System.out.println("После map: " + n)) // Только для отладки
.filter(n -> n > 2)
.peek(n -> System.out.println("После filter: " + n))
.collect(Collectors.toList());
System.out.println(result); // [4, 6]
// Вывод отладки:
// После map: 2
// После map: 4
// После filter: 4
// После map: 6
// После filter: 6
Терминальные операции
Терминальные операции заканчивают конвейер обработки и возвращают финальный результат (не Stream). Они немедленно выполняют всю цепочку нетерминальных операций.
Основные терминальные операции:
1. collect() — собрать в коллекцию
List<String> words = Arrays.asList("apple", "banana", "cherry");
// В List
List<String> list = words.stream()
.filter(w -> w.length() > 5)
.collect(Collectors.toList());
// В Set
Set<String> set = words.stream()
.collect(Collectors.toSet());
// В String (конкатенация)
String joined = words.stream()
.collect(Collectors.joining(", "));
System.out.println(joined); // apple, banana, cherry
// В Map
Map<String, Integer> wordLengths = words.stream()
.collect(Collectors.toMap(
word -> word,
String::length
));
2. forEach() — для каждого элемента
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(name -> System.out.println("Hello, " + name));
// Вывод:
// Hello, Alice
// Hello, Bob
// Hello, Charlie
3. reduce() — сведение к одному значению
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Сумма
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum); // 15
// Произведение
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println(product); // 120
// С Optional (без начального значения)
Optional<Integer> max = numbers.stream()
.reduce((a, b) -> a > b ? a : b);
max.ifPresent(System.out::println); // 5
4. count() — количество элементов
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
long count = words.stream()
.filter(w -> w.length() > 4)
.count();
System.out.println(count); // 3 (apple, banana, cherry)
5. anyMatch(), allMatch(), noneMatch() — проверка условия
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Есть ли чётные?
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0);
System.out.println(hasEven); // true
// Все ли > 0?
boolean allPositive = numbers.stream()
.allMatch(n -> n > 0);
System.out.println(allPositive); // true
// Нет ли > 10?
boolean noneGreaterThan10 = numbers.stream()
.noneMatch(n -> n > 10);
System.out.println(noneGreaterThan10); // true
6. findFirst() и findAny() — найти первый/любой
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Первый чётный
Optional<Integer> first = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
first.ifPresent(System.out::println); // 2
// Любой элемент (может отличаться в многопоточности)
Optional<Integer> any = numbers.stream()
.findAny();
any.ifPresent(System.out::println);
7. min() и max() — минимум и максимум
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
Optional<Integer> min = numbers.stream()
.min(Comparator.naturalOrder());
min.ifPresent(System.out::println); // 1
Optional<Integer> max = numbers.stream()
.max(Comparator.naturalOrder());
max.ifPresent(System.out::println); // 9
Сравнение терминальных и нетерминальных
| Характеристика | Нетерминальные | Терминальные |
|---|---|---|
| Возвращаемый тип | Stream | Значение, Optional, void |
| Выполнение | Ленивое (отложенное) | Немедленное |
| Результат | Промежуточный поток | Финальный результат |
| Можно цепировать | ✓ Да | ✗ Нет (конец цепи) |
| Примеры | map, filter, sorted | collect, forEach, count |
Ленивое вычисление (Lazy Evaluation)
Это ключевая особенность нетерминальных операций:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// БЕЗ терминальной операции - ничего не выполняется
Stream<Integer> stream = numbers.stream()
.map(n -> {
System.out.println("Вычисляю: " + n);
return n * 2;
})
.filter(n -> {
System.out.println("Фильтрую: " + n);
return n > 4;
});
// Ничего не выведется! Операции не выполнены
// С терминальной операцией - выполняется цепочка
List<Integer> result = numbers.stream()
.map(n -> {
System.out.println("Вычисляю: " + n);
return n * 2;
})
.filter(n -> {
System.out.println("Фильтрую: " + n);
return n > 4;
})
.collect(Collectors.toList()); // ← Терминальная операция
// Вывод (обработка идёт по элементам, не по операциям):
// Вычисляю: 1
// Фильтрую: 2
// Вычисляю: 2
// Фильтрую: 4
// Вычисляю: 3
// Фильтрую: 6
// Вычисляю: 4
// Фильтрую: 8
// Вычисляю: 5
// Фильтрую: 10
System.out.println(result); // [6, 8, 10]
Практический пример: Полный конвейер
public class StreamExample {
static class User {
String name;
int age;
List<String> skills;
public User(String name, int age, List<String> skills) {
this.name = name;
this.age = age;
this.skills = skills;
}
}
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", 25, Arrays.asList("Java", "SQL")),
new User("Bob", 32, Arrays.asList("Python", "ML")),
new User("Charlie", 22, Arrays.asList("Java", "JavaScript")),
new User("Diana", 28, Arrays.asList("Go", "Rust"))
);
// Найти всех пользователей 25+, с Java навыком, отсортировать по имени
List<String> result = users.stream()
// Нетерминальная: filter
.filter(user -> user.age >= 25)
// Нетерминальная: filter
.filter(user -> user.skills.contains("Java"))
// Нетерминальная: map
.map(user -> user.name)
// Нетерминальная: sorted
.sorted()
// Терминальная: collect
.collect(Collectors.toList());
System.out.println(result); // [Alice, Charlie]
}
}
Заключение
Нетерминальные операции (map, filter, sorted, etc.):
- Возвращают Stream
- Ленивое вычисление (пока нет терминала)
- Можно цепировать
Терминальные операции (collect, forEach, count, etc.):
- Возвращают финальный результат
- Немедленное выполнение
- Конец цепочки
Помни: без терминальной операции нетерминальные операции вообще не выполняются!