JavaRush /Курсы /JAVA 25 SELF /Базовые операции Stream API: map, filter, collect

Базовые операции Stream API: map, filter, collect

JAVA 25 SELF
30 уровень , 1 лекция
Открыта

1. Создаём поток

Чтобы использовать Stream API, нужно сначала получить поток из какой-то коллекции или массива.

Примеры создания потока

// Из списка
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();

// Из массива
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);

// Из отдельных значений
Stream<String> letters = Stream.of("A", "B", "C");

Коротко:

  • list.stream() — для коллекций
  • Arrays.stream(array) — для массивов
  • Stream.of(...) — для отдельных значений

Пример в контексте нашего приложения

Допустим, у нас есть список пользователей:

List<String> users = List.of("Ivan", "Anna", "Petr", "Alexey");
Stream<String> userStream = users.stream();

Промежуточные и терминальные операции

Важный момент: операции в Stream API делятся на два типа.

  • Промежуточные операции (например, filter, map, distinct) — описывают этапы обработки. Они возвращают новый поток, но сами по себе ничего не запускают.
  • Терминальные операции (например, collect, forEach, count) — запускают конвейер и выдают результат.

Поток работает «лениво»: пока не вызвана терминальная операция, никаких вычислений не произойдёт. Именно поэтому мы часто заканчиваем цепочку collect(...) — это и есть точка, где поток превращается обратно в коллекцию или другой результат.

2. Операция filter: фильтруем элементы по условию

filter — это промежуточная операция, которая пропускает только те элементы, которые соответствуют заданному условию.

Сигнатура

Stream<T> filter(Predicate<? super T> predicate); 

Predicate — это функциональный интерфейс, принимающий элемент и возвращающий true (оставить) или false (отсеять).

Пример: оставить только чётные числа

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

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Что происходит?

  • n -> n % 2 == 0 — лямбда-выражение, проверяет, делится ли число на 2 без остатка.
  • filter оставляет только чётные числа.

Пример: фильтруем имена, начинающиеся с "A"

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<String> aNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(aNames); // [Anna, Alex, Alina]

Важный момент: filter не меняет коллекцию — он создаёт новый поток, в котором есть только нужные элементы.

3. Операция map: превращаем элемент в нечто другое

map — это операция преобразования. Она берёт каждый элемент потока, применяет к нему функцию и возвращает новый элемент.

Сигнатура

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Function — это интерфейс, принимающий элемент и возвращающий что-то (может быть другого типа).

Пример: получить длины строк

List<String> names = List.of("Anna", "Boris", "Alex");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

System.out.println(nameLengths); // [4, 5, 4]

Что происходит?

  • map превращает строку в её длину (name -> name.length()).
  • В результате получается поток чисел.

Пример: привести строки к верхнему регистру

List<String> names = List.of("Anna", "Boris", "Alex");

List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // [ANNA, BORIS, ALEX]

4. Операция collect: собираем результат обратно в коллекцию

collect — это терминальная операция, то есть она завершает работу потока и собирает результат в коллекцию или другой контейнер.

Сигнатура

<R, A> R collect(Collector<? super T, A, R> collector)

Не пугайтесь страшной сигнатуры! В 99% случаев вы используете готовые коллекторы из класса Collectors.

Collectors — это утилитный класс с набором «сборщиков». Он говорит потоку, в какую форму собрать результат: список, множество, строку и т. д.

Примеры:

  • Collectors.toList() — в List
  • Collectors.toSet() — в Set
  • Collectors.joining(", ") — в строку через запятую

То есть Collectors — это как набор коробок разной формы, в которые вы упаковываете элементы потока.

Пример: собрать результат в List

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

Пример: собрать результат в Set

Set<String> uniqueNames = names.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toSet());

Пример: собрать строки через запятую

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result); // Anna, Boris, Alex

5. Цепочка операций: фильтрация + преобразование + сбор результата

Самая большая сила Stream API — это возможность цеплять операции друг за другом.

Пример: получить длины имён, начинающихся с "A"

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<Integer> aNameLengths = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(aNameLengths); // [4, 4, 5]

Пошагово:

  1. .stream() — создаём поток из списка.
  2. .filter(name -> name.startsWith("A")) — оставляем только имена, начинающиеся на "A".
  3. .map(String::length) — превращаем каждое имя в его длину.
  4. .collect(Collectors.toList()) — собираем результат в список.

Аналогичный императивный код

Вот как бы выглядело то же самое "по-старинке":

List<Integer> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.length());
    }
}

Сравните: Stream API — одна строка, читается как «что делаем», а не «как делаем».

6. Практика: несколько коротких задач

Потренируемся! Все примеры можно запускать в одном файле — просто меняйте данные.

Задача 1: Оставить только нечётные числа и возвести их в квадрат

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);

List<Integer> oddSquares = numbers.stream()
    .filter(n -> n % 2 != 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(oddSquares); // [1, 9, 25, 49]

Задача 2: Из списка строк получить список их первых букв

List<String> names = List.of("Anna", "Boris", "Alex");

List<Character> initials = names.stream()
    .map(name -> name.charAt(0))
    .collect(Collectors.toList());

System.out.println(initials); // [A, B, A]

Задача 3: Отфильтровать строки длиной больше 3 и собрать их в Set

List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");

Set<String> longWords = words.stream()
    .filter(word -> word.length() > 3)
    .collect(Collectors.toSet());

System.out.println(longWords); // [bear, elephant]

7. Типичные ошибки при работе с filter, map, collect

Ошибка № 1: забыли collect — результата нет!
Stream API ленивый, как кот на подоконнике: пока не вызовешь терминальную операцию (например, collect или forEach), ничего не произойдёт. Если написать только users.stream().filter(...).map(...); — это не выполнит никаких действий.

Ошибка № 2: filter и map перепутаны местами
Иногда новички сначала делают map, а потом filter. Например, names.stream().map(String::length).filter(len -> len > 3) — это даст числа, а не строки. Если вам нужны строки длиной больше 3, сначала фильтруйте, потом преобразовывайте.

Ошибка № 3: забыли про неизменяемость
Операции Stream API не меняют исходную коллекцию! Они возвращают новый результат. После List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — коллекция names останется прежней.

Ошибка № 4: попытка использовать внешний изменяемый список
Не стоит делать так:

List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));

Лучше использовать collect — это безопаснее и короче.

Ошибка № 5: NullPointerException
Если в коллекции могут быть null-элементы, то вызов name.startsWith("A") на null даст ошибку. Добавляйте фильтр на null, если это возможно:

.filter(name -> name != null && name.startsWith("A"))
1
Задача
JAVA 25 SELF, 30 уровень, 1 лекция
Недоступна
Инвентаризация в Волшебной Лавке 🍎
Инвентаризация в Волшебной Лавке 🍎
1
Задача
JAVA 25 SELF, 30 уровень, 1 лекция
Недоступна
Мониторинг Датчиков в Лаборатории 🧪
Мониторинг Датчиков в Лаборатории 🧪
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Big198801 Уровень 33
29 января 2026
Хорошая подача материала. Интересно, как там дальше по стримам будет - мощный инструмент)
Сергей Уровень 42
1 октября 2025
кайф
Ioanna Polyak Уровень 44
11 ноября 2025
согл