JavaRush /Курсы /JAVA 25 SELF /Использование лямбда в коллекциях и стримах

Использование лямбда в коллекциях и стримах

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

1. Лямбда-выражения и коллекции

Вспомним, как выглядела обработка коллекций до появления лямбда-выражений. Допустим, у нас есть список строк, и мы хотим вывести их на экран:

List<String> list = Arrays.asList("кот", "пёс", "ёж");

for (String s : list) {
    System.out.println(s);
}

Всё просто, но если мы хотим, например, удалить из списка все пустые строки, приходится писать цикл с условием, а иногда и использовать итератор (иначе будет ConcurrentModificationException). Или, скажем, сортировка по длине строки:

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Даже на такой простой задаче — уже 5 строк кода и куча «шумных» скобок. Как оптимизировать? Ответ вы уже знаете: лямбда-выражения.

Применение лямбда-выражений в методах коллекций

С Java 8 интерфейсы коллекций получили новые методы, которые принимают функциональные интерфейсы — а значит, мы можем передавать туда лямбда-выражения. Вот самые популярные из них:

  • forEach(Consumer<T> action)
  • removeIf(Predicate<T> filter)
  • sort(Comparator<T> c)
  • replaceAll(UnaryOperator<T> operator)

Пример: forEach

Вывести все элементы списка на экран (старый способ):

for (String s : list) {
    System.out.println(s);
}

Теперь — с лямбдой:

list.forEach(s -> System.out.println(s));

Или ещё короче, если хочется поиграть в «гуру Java»:

list.forEach(System.out::println); // method reference, разберём позже

Пример: removeIf

Удалить все пустые строки из списка:

List<String> animals = new ArrayList<>(Arrays.asList("кот", "", "пёс", "ёж", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [кот, пёс, ёж]

Пример: sort

Сортировка списка по длине строки:

List<String> animals = Arrays.asList("кот", "пёс", "ёж", "слон");

animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [ёж, кот, пёс, слон]

Пример: replaceAll

Преобразовать все строки к верхнему регистру:

List<String> animals = new ArrayList<>(Arrays.asList("кот", "пёс", "ёж"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [КОТ, ПЁС, ЁЖ]

2. Stream API и лямбда-выражения

С выходом Java 8 появился Stream API — мощный инструмент для обработки коллекций в функциональном стиле. Потоки позволяют фильтровать, преобразовывать, сортировать, собирать коллекции с помощью цепочек методов. И все эти методы принимают лямбда-выражения!

Важно: Полный разбор Stream API будет позже, сейчас — только базовые примеры для понимания роли лямбд.

Пример: фильтрация

Оставить только строки длиннее 3 символов:

List<String> animals = Arrays.asList("кот", "слон", "ёж", "крокодил");
animals.stream()
       .filter(s -> s.length() > 3)
       .forEach(System.out::println); // слон, крокодил

Пример: преобразование (map)

Сделать все строки заглавными:

List<String> animals = Arrays.asList("кот", "слон", "ёж");
List<String> upper = animals.stream()
                            .map(s -> s.toUpperCase())
                            .collect(Collectors.toList());
System.out.println(upper); // [КОТ, СЛОН, ЁЖ]

Пример: сортировка

Получить отсортированный по длине список (не меняя исходный):

List<String> animals = Arrays.asList("кот", "слон", "ёж", "крокодил");
List<String> sorted = animals.stream()
                             .sorted((a, b) -> a.length() - b.length())
                             .collect(Collectors.toList());
System.out.println(sorted); // [ёж, кот, слон, крокодил]

3. Сравнение с анонимными классами

Давайте сравним, как выглядел бы тот же код с анонимным классом и с лямбдой.

Сортировка по длине строки

Анонимный класс:

animals.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Лямбда-выражение:

animals.sort((a, b) -> a.length() - b.length());

Вывод:
Лямбда-выражение экономит кучу строк и делает код более читабельным. Меньше скобок, меньше шума — больше сути!

Удаление пустых строк

Анонимный класс:

animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty();
    }
});

Лямбда-выражение:

animals.removeIf(s -> s.isEmpty());

4. Практика: короткие задачи с лямбда-выражениями

Давайте попробуем на практике применить лямбда-выражения в мини-программе для работы со списком пользователей.

Пример 1: Фильтрация пользователей по возрасту

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

List<User> users = Arrays.asList(
    new User("Алиса", 17),
    new User("Боб", 25),
    new User("Чарли", 15)
);

users.stream()
     .filter(u -> u.age >= 18)
     .forEach(System.out::println); // Боб (25)

Пример 2: Сортировка пользователей по имени

List<User> users = Arrays.asList(
    new User("Алиса", 17),
    new User("Боб", 25),
    new User("Чарли", 15)
);

users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Алиса (17), Боб (25), Чарли (15)]

Пример 3: Преобразование списка пользователей в список имён

List<String> names = users.stream()
                          .map(u -> u.name)
                          .collect(Collectors.toList());
System.out.println(names); // [Алиса, Боб, Чарли]

Пример 4: Удалить всех несовершеннолетних

List<User> users = new ArrayList<>(Arrays.asList(
    new User("Алиса", 17),
    new User("Боб", 25),
    new User("Чарли", 15)
));

users.removeIf(u -> u.age < 18);
System.out.println(users); // [Боб (25)]

5. Полезные нюансы

Особенности: область видимости и «эффективно final» переменные

Лямбда-выражение может использовать переменные из внешнего метода, но только если они final или «эффективно final» (то есть не изменяются после инициализации).

int minAge = 18;
users.stream()
     .filter(u -> u.age >= minAge)
     .forEach(System.out::println);

Если вы попробуете изменить minAge после использования в лямбде — компилятор выдаст ошибку.

Таблица: основные методы коллекций и стримов с лямбда-выражениями

Метод коллекции/стрима Что делает Тип лямбда-выражения Пример
forEach(Consumer<T>)
Для каждого элемента
x -> ...
list.forEach(s -> ...)
removeIf(Predicate<T>)
Удаляет элементы по условию
x -> ...
list.removeIf(s -> ...)
sort(Comparator<T>)
Сортирует элементы
(a, b) -> ...
list.sort((a, b) -> ...)
replaceAll(UnaryOperator<T>)
Заменяет каждый элемент
x -> ...
list.replaceAll(s -> ...)
filter(Predicate<T>)
Фильтрует поток
x -> ...
stream.filter(s -> ...)
map(Function<T, R>)
Преобразует элементы
x -> ...
stream.map(s -> ...)
forEach(Consumer<T>)
Итерация по потоку
x -> ...
stream.forEach(s -> ...)
sorted(Comparator<T>)
Сортировка в потоке
(a, b) -> ...
stream.sorted((a, b) -> ...)

7. Типичные ошибки

Ошибка №1: Лямбда слишком длинная. Если внутри лямбда-выражения у вас уже 5 строк кода, условия, циклы и try-catch — скорее всего, стоит вынести этот код в отдельный метод. Лямбды хороши для короткой логики.

Ошибка №2: Использование изменяемых переменных. Если вы пытаетесь внутри лямбды изменить переменную из внешнего метода (например, счётчик), компилятор не даст этого сделать. Переменная должна быть final или эффективно final.

Ошибка №3: Забыли, что методы коллекций/стримов не всегда меняют исходную коллекцию. Например, stream().filter(...) не изменяет исходный список, а возвращает новый поток. Если хотите получить коллекцию — используйте collect(Collectors.toList()).

Ошибка №4: Лямбда-выражение не подходит по типу. Если метод принимает, например, Comparator<T>, а вы пытаетесь передать лямбду с одним параметром (а не двумя) — будет ошибка компиляции.

Ошибка №5: Теряете читаемость при вложенных лямбдах. Если у вас цепочка из map, filter, forEach, и внутри каждой лямбды ещё одна лямбда — код становится нечитаемым. В таких случаях лучше разбить выражения на отдельные шаги или вынести части в методы.

1
Задача
JAVA 25 SELF, 48 уровень, 1 лекция
Недоступна
Система "КРИКЛИВЫХ" Продуктов 📢
Система "КРИКЛИВЫХ" Продуктов 📢
1
Задача
JAVA 25 SELF, 48 уровень, 1 лекция
Недоступна
Экспедиция "Великие Воды" 🗺️
Экспедиция "Великие Воды" 🗺️
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Александр Уровень 50
19 декабря 2025
Позже может и разберусь, но сейчас не догоняю такой момент:

List<String> list = waterBodies.stream()
                .map(word -> word.toUpperCase())
В методе map крутятся объекты типа Function и они должны реализовывать метод

R apply(T t);
Получается, типы T и R могут быть одинаковы? Зачем тогда городили вот такой огород:

UnaryOperator<T> extends Function<T, T>
T apply(T t);
Ведь родитель

interface Function<T, R>
R apply(T t);
вполне может работать с одним типом?