Терминальные операции
Терминальная операция запускает pipeline Stream API и завершает stream.
Результатом может быть значение, коллекция, Optional или только побочный
эффект.
Группы операций
| Задача | Методы |
|---|---|
| количество | count |
| поиск | findFirst, findAny |
| проверка | anyMatch, allMatch, noneMatch |
| экстремум | min, max |
| свёртка | reduce |
| накопление | collect, toList |
| обход | forEach, forEachOrdered |
| массив | toArray |
find... и match-операции могут завершиться раньше полного обхода.
findFirst уважает encounter order, а findAny оставляет больше свободы
параллельному выполнению.
Optional как результат
Optional<User> oldest = users.stream()
.max(Comparator.comparingInt(User::age));
String name = oldest.map(User::name).orElse("нет данных");min, max, findFirst, findAny возвращают Optional, потому что stream
может быть пустым.
Reduce
int sum = numbers.stream().reduce(0, Integer::sum);Identity должен быть нейтральным элементом, а accumulator — ассоциативным, если pipeline может стать parallel. Ошибочная identity и неассоциативная операция дают разные результаты при разном разбиении.
Для построения изменяемой коллекции обычно нужен collect, а не reduce:
Map<String, Long> counts = words.stream()
.collect(Collectors.groupingBy(
String::toLowerCase,
Collectors.counting()));Полезные collectors
| Collector | Назначение |
|---|---|
toList, toSet | собрать элементы |
joining | соединить строки |
groupingBy | сгруппировать по ключу |
partitioningBy | разбить по boolean-условию |
mapping, filtering | downstream-преобразование |
summarizingInt | count, sum, min, average, max |
toMap | построить map |
При повторяющемся ключе двухаргументный toMap(key, value) бросает
IllegalStateException. Правило объединения задают третьим аргументом:
Map<String, User> byEmail = users.stream()
.collect(Collectors.toMap(
User::email,
Function.identity(),
(first, duplicate) -> first));Stream.toList() возвращает неизменяемый список. Конкретный тип результата
Collectors.toList() не гарантирован, и изменяемость не является частью его
контракта; если нужен ArrayList, используется
toCollection(ArrayList::new).
ForEach и порядок
forEach в parallel stream не сохраняет encounter order.
forEachOrdered сохраняет, но может уменьшить параллелизм. Для вычисления
результата предпочтительнее collect/reduce, чем запись в общий mutable список.
Промежуточная часть pipeline: Промежуточные операции.
Вопросы для собеседования
- Что происходит со stream после terminal-операции? Он считается потреблённым.
- Чем findFirst отличается от findAny? Первый сохраняет порядок, второй допускает любой элемент.
- Почему reduce требует ассоциативности? Частичные результаты могут объединяться в разном порядке.
- Что случится с дубликатом ключа в простом toMap? Исключение.
- Когда collect лучше reduce? При накоплении в mutable container.
- Изменяем ли результат Stream.toList? Нет.
- Сохраняет ли parallel forEach порядок? Нет; для этого есть forEachOrdered.