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, і всередині кожної лямбди ще одна лямбда — код стає нечитабельним. У таких випадках краще розбити вирази на окремі кроки або винести частини в методи.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ