JavaRush /Курси /JAVA 25 SELF /Анонімні класи: відмінність від лямбда-виразів, приклади

Анонімні класи: відмінність від лямбда-виразів, приклади

JAVA 25 SELF
Рівень 48 , Лекція 4
Відкрита

1. Заглиблюємося в анонімні класи

Анонімний клас — це безіменний підклас або реалізація інтерфейсу, що створюється прямо в місці використання. До появи лямбд (Java 8) це був найзручніший спосіб «одноразової» реалізації інтерфейсу або абстрактного класу.

Класика жанру:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Привіт з анонімного класу!");
    }
};
r.run();

Тут ми оголосили й одразу реалізували інтерфейс Runnable — без окремого файлу та імені класу. Такі реалізації часто використовували для обробників подій, компараторів, потоків та інших завдань, де потрібно швидко «підкинути» поведінку.

Якщо лямбда — це «вираз на льоту», то анонімний клас — «маленький актор без імені», який зіграв епізодичну роль і зник.

2. Порівняння з лямбда-виразами

Синтаксис

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

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

Лямбда-вираз:

Comparator<String> comp = (a, b) -> a.length() - b.length();

Різниця очевидна: лямбда компактніша — не потрібно явно прописувати типи, ім’я методу й зайві фігурні дужки, якщо дія проста.

Функціональність

  • Анонімний клас — повноцінний об’єкт. Можна оголошувати поля, додаткові методи, перевизначати методи Object (toString, equals тощо).
  • Лямбда-вираз — реалізація одного абстрактного методу функціонального інтерфейсу. Усередині не можна оголошувати власні поля або додаткові методи.

Коли що обирати?

  • Лямбда — коли потрібно коротко реалізувати один метод функціонального інтерфейсу.
  • Анонімний клас — коли потрібно:
    • реалізувати кілька методів (наприклад, абстрактного класу);
    • оголосити поля для стану;
    • перевизначити методи Object (наприклад, toString);
    • використовувати особливості успадкування/доступу (наприклад, до захищених членів суперкласу).

3. Область видимості та ключове слово this

Тут криється часта пастка:

  • в анонімному класі this посилається на екземпляр анонімного класу;
  • у лямбда-виразі this посилається на зовнішній клас, у якому оголошено лямбду.

Приклад: порівняймо поведінку

public class Outer {
    String name = "Зовнішній клас";

    void test() {
        Runnable anon = new Runnable() {
            String name = "Анонімний клас";
            @Override
            public void run() {
                System.out.println(this.name); // "Анонімний клас"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "Зовнішній клас"

        anon.run();
        lambda.run();
    }
}

Виведення:

Анонімний клас
Зовнішній клас

В анонімному класі this вказує на сам анонімний клас (зчитується його поле name). У лямбді this — це Outer.

4. Коли використовувати анонімні класи?

Якщо потрібно реалізувати більше ніж один метод

Лямбда працює тільки з функціональними інтерфейсами (рівно один абстрактний метод). Якщо інтерфейс/абстрактний клас вимагає реалізувати кілька методів — потрібен анонімний клас.

abstract class Animal {
    abstract void say();
    abstract void jump();
}

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("Няв!");
    }
    @Override
    void jump() {
        System.out.println("Стриб!");
    }
};

Якщо потрібно зберігати стан (поля)

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void run() {
        counter++;
        System.out.println("Викликано " + counter + " раз(и)");
    }
};
r.run(); // Викликано 1 раз(и)
r.run(); // Викликано 2 раз(и)

Якщо потрібно перевизначити методи Object

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "Компаратор за довжиною рядка";
    }
};
System.out.println(comp); // Компаратор за довжиною рядка

5. Приклади: Comparator і Runnable — лямбда vs анонімний клас

Сортування рядків за довжиною

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

List<String> words = Arrays.asList("кіт", "слон", "миша", "тигр");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

Лямбда-вираз:

List<String> words = Arrays.asList("кіт", "слон", "миша", "тигр");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

Результат однаковий, але код із лямбдою коротший і легше читається.

Runnable: запуск потоку

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

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Потік через анонімний клас");
    }
});
t1.start();

Лямбда-вираз:

Thread t2 = new Thread(() -> System.out.println("Потік через лямбду"));
t2.start();

Анонімний клас із полями

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void run() {
        count++;
        System.out.println("Викликано " + count + " раз(и)");
    }
};
r.run(); // Викликано 1 раз(и)
r.run(); // Викликано 2 раз(и)

У лямбді так не можна — немає можливості оголосити поле.

6. Особливості: область видимості, змінні та final

І в анонімних класах, і в лямбда-виразах локальні змінні зовнішнього методу можна використовувати лише якщо вони final або «ефективно final» (не змінюються після ініціалізації). Але є нюанс із іменами:

  • в анонімному класі можна оголосити змінну з тим самим іменем, що й у зовнішній області («затінення»);
  • у лямбді — не можна: ім’я не повинно конфліктувати з іменем зовнішньої змінної.

Приклад:

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void run() {
        int x = 20; // ОК: затінює зовнішню змінну
        System.out.println(x); // 20
    }
};
r.run();

Runnable l = () -> {
    // int x = 30; // Помилка компіляції: змінну вже визначено
    System.out.println(x); // 10
};
l.run();

7. Коли лямбда — кращий вибір, а коли анонімний клас — незамінний?

Лямбда-вирази — ваш вибір, якщо:

  • потрібно реалізувати коротку функцію для функціонального інтерфейсу;
  • не потрібно зберігати стан;
  • не потрібно перевизначати методи Object;
  • реалізація використовується «тут і зараз» і є простою.

Анонімний клас — необхідний, якщо:

  • треба реалізувати інтерфейс із кількома методами або абстрактний клас;
  • потрібно оголосити поля або додаткові методи;
  • потрібно перевизначити toString, equals, hashCode;
  • потрібен доступ до захищених членів суперкласу.

8. Практика: порівняння на прикладах

Завдання 1: Фільтрація списку через Predicate

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

List<String> animals = Arrays.asList("кіт", "слон", "миша", "тигр");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [слон, миша, тигр]

Лямбда-вираз:

List<String> animals = Arrays.asList("кіт", "слон", "миша", "тигр");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [слон, миша, тигр]

Завдання 2: Порівняння області видимості this

public class Demo {
    String name = "Demo";

    void check() {
        Runnable anon = new Runnable() {
            String name = "Anon";
            @Override
            public void run() {
                System.out.println(this.name); // "Anon"
            }
        };

        Runnable lambda = () -> System.out.println(this.name); // "Demo"

        anon.run();
        lambda.run();
    }

    public static void main(String[] args) {
        new Demo().check();
    }
}

9. Типові помилки при роботі з анонімними класами та лямбда-виразами

Помилка № 1: Очікування, що лямбда може реалізувати кілька методів. Лямбда працює тільки з функціональними інтерфейсами (один абстрактний метод). Якщо методів більше — використовуйте анонімний клас.

Помилка № 2: Плутанина з областю видимості this. У лямбді this — це зовнішній клас, в анонімному класі — сам анонімний клас. Через це легко отримати «не ті» поля та значення.

Помилка № 3: Спроба оголосити поля в лямбді. У лямбді не можна оголошувати власні поля — лише використовувати змінні зовнішнього контексту (final/«ефективно final»). Для стану використовуйте анонімний клас.

Помилка № 4: Затінення змінних. В анонімному класі можна оголосити локальну змінну з тим самим іменем, що у зовнішньої — це затінення. У лямбді так не можна: компілятор видасть помилку.

Помилка № 5: Занадто складна логіка в лямбді. Якщо тіло лямбди стає довшим за 35 рядків, читабельність страждає. Краще винести код в окремий метод або застосувати анонімний клас (якщо потрібен стан/кілька методів).

1
Опитування
Лямбда-вирази, рівень 48, лекція 4
Недоступний
Лямбда-вирази
Лямбда-вирази
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ