map / filter / collect
Stream — это конвейер обработки коллекции. Вместо ручного цикла ты описываешь, что сделать: отфильтровать → преобразовать → собрать. Читается как предложение и не меняет исходные данные.
Базовый конвейер
List<String> names = List.of("Аня", "Боб", "Виктор");
List<String> result = names.stream() // 1. открыли поток
.filter(n -> n.length() > 3) // 2. оставили длинные
.map(String::toUpperCase) // 3. преобразовали
.collect(Collectors.toList()); // 4. собрали обратно
// [ВИКТОР]
Три кита, которых хватает на 80% задач:
| Метод | Что делает |
|---|---|
filter(p) | оставляет элементы по условию |
map(f) | преобразует каждый элемент |
collect(...) | собирает результат в коллекцию |
Копнуть глубже — частые промежуточные и терминальные методы
Промежуточные (возвращают Stream, можно цепочкой):
| Метод | Что делает |
|---|---|
sorted() / sorted(cmp) | сортировка |
distinct() | убрать дубликаты |
limit(n) / skip(n) | первые n / пропустить n |
flatMap(f) | развернуть вложенные коллекции |
Терминальные (завершают поток):
| Метод | Что делает |
|---|---|
forEach(a) | действие для каждого |
count() | количество |
reduce((a,b)->a+b) | свернуть в одно значение |
anyMatch / allMatch / noneMatch | проверки на условие |
findFirst() | первый элемент (как Optional) |
Collectors (для collect): toList(), toSet(), toMap(k,v), joining(", "), groupingBy(...), counting(), summingInt(...).
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::city));
Под капотом — для глубокого понимания
Stream ленивый: промежуточные методы ничего не выполняют, пока не вызван терминальный. Тогда элементы проходят конвейер по одному, а не «весь filter, потом весь map» — это экономит проходы. parallelStream() распараллеливает обработку через ForkJoinPool, но выгоден только на больших объёмах и без побочных эффектов внутри лямбд (иначе словишь гонки данных).
• в чём разница промежуточных и терминальных методов;
• что значит «ленивость» стрима (если дошёл до 3-го слоя).