JavaRush /Курси /JAVA 25 SELF /Анонімні класи

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

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

1. Знайомство з анонімними класами

Уявіть, що у вас є клас Animal, який описує поведінку тварини. Цей клас має метод say(), що виводить "Тварина видає звук". Вам потрібно створити об’єкт, який поводитиметься як собака й виводитиме "Гав!", але ви не хочете створювати для цього окремий файл Dog.java.

Саме тут у пригоді стануть анонімні класи. Це класи без імені, які оголошують і створюють безпосередньо у місці їхнього використання. 🚀 Вони дають змогу «на льоту» успадкувати клас, не створюючи окремий файл. Замість того, щоб створювати безліч невеликих файлів із класами, які потрібні лише в одному місці, ви можете написати всю логіку прямо там, де вона потрібна.

Як виглядає синтаксис?

Синтаксис анонімних класів може здатися незвичним, але насправді він доволі простий:

ТипПеременной имя = new ТипДляНаследования() {
    // Тут ми пишемо тіло анонімного класу
    // Перевизначаємо методи батьківського класу
};

Розберімо цей синтаксис:

  • ТипПеременной имя: Це звичайне оголошення змінної, куди ми збережемо новий об’єкт.
  • new ТипДляНаследования(): Тут ми ніби створюємо новий об’єкт. Але замість імені класу ми використовуємо ім’я класу, від якого хочемо успадковуватися. Зверніть увагу: після дужок () немає крапки з комою!
  • { ... }: Тут ми відкриваємо фігурні дужки й пишемо всю логіку нашого анонімного класу. Можна перевизначити методи батьківського класу або додати власну логіку.

Приклад успадкування від звичайного класу:

Повернімося до нашого прикладу з твариною.

class Animal {
    void say() {
        System.out.println("Тварина видає звук");
    }
}

// Створюємо анонімний клас, успадковуючись від Animal
Animal dog = new Animal() {
    // Перевизначаємо метод say()
    @Override
    void say() {
        System.out.println("Гав-гав! 🐶");
    }
};

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("Няв-няв! 🐱");
    }
};

dog.say(); // Виведе: Гав-гав! 🐶
cat.say(); // Виведе: Няв-няв! 🐱

У цьому прикладі ми створили два об’єкти, dog і cat, які фактично є анонімними класами, що успадковуються від Animal. Водночас ми не створили жодного окремого файлу.

Порівняння підходів

// Звичайний спосіб (створюємо окремий клас)
class Dog extends Animal {
@Override
void say() { System.out.println("Гав!"); }
}
Animal dog = new Dog();

// VS

// Анонімний клас (усе в одному місці)
Animal dog = new Animal() {
@Override
void say() { System.out.println("Гав!"); }
};

Результат однаковий, але анонімний клас коротший і не захаращує проєкт!

2. Ім’я анонімного класу після компіляції

Анонімні класи не мають імені в початковому коді, але компілятор Java все одно має якось їх назвати, щоб створити .class-файл. Він робить це за таким правилом:

  • Ім’я файлу анонімного класу складається з імені зовнішнього класу, у якому його оголошено.
  • Після імені зовнішнього класу додається знак долара $.
  • Далі йде порядковий номер анонімного класу в цьому файлі, починаючи з 1.

Отже, якщо наш приклад із тваринами міститься у файлі Main.java, після компіляції буде створено три файли:

  • Main.class
  • Main$1.class (наш анонімний клас для собаки)
  • Main$2.class (наш анонімний клас для кішки)

Якщо анонімний клас оголошено всередині методу, що сам міститься у внутрішньому класі, ім’я буде складнішим, наприклад, OuterClass$InnerClass$1.class.

Це внутрішня домовленість компілятора, про яку корисно знати, але в повсякденній розробці вона рідко має вирішальне значення. Головне — пам’ятати, що анонімний клас усе одно є повноцінним класом, хоч і без імені в початковому коді.

3. Важливі особливості та обмеження

Анонімні класи — це потужний інструмент, але в них є свої правила.

Доступ до змінних. Анонімний клас може використовувати змінні з навколишнього методу. Однак ці змінні мають бути final або effectively final (тобто їхнє значення не змінюється після ініціалізації).

public void doSomething() {
    String greeting = "Привіт!"; // Ця змінна effectively final

    class OuterClass {
        void greet() {
            // Створюємо анонімний клас усередині методу
            new Object() {
                void sayHello() {
                    System.out.println(greeting); // Це дозволено
                    // greeting = "Бувай!"; // А це викличе помилку!
                }
            }.sayHello();
        }
    }

    new OuterClass().greet();
}

Чому так? Тому що анонімний клас може «жити» довше, ніж сам метод, і якби він міг змінювати змінну, це призвело б до проблем.

Немає конструктора. Оскільки в анонімного класу немає імені, у нього не може бути конструктора. Але ви можете використовувати блок ініціалізації для виконання коду під час створення об’єкта:

Animal dog = new Animal() {
    // Блок ініціалізації
    {
        System.out.println("Ініціалізація анонімного класу 🐶");
    }
    @Override
    void say() {
        System.out.println("Гав-гав!");
    }
};

Обмеження. Анонімні класи не можуть оголошувати статичні поля (окрім констант) або методи. Вони завжди створюються як частина іншого об’єкта, тому не можуть бути static, public, protected або private.

4. Корисні нюанси

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

  • Вам потрібно успадкувати клас (або реалізувати інтерфейс) лише один раз.
  • Реалізація невелика — 1–2 методи, не більше кількох десятків рядків коду.
  • Клас потрібен лише в одному місці й немає сенсу давати йому ім’я.
  • Хочете уникнути «захаращення» пакета безліччю дрібних одноразових класів.

Типові сценарії:

  • Обробники подій (GUI, Swing, Android тощо).
  • Передача колбеків (callback) у методи.
  • Швидка реалізація компараторів для сортування колекцій.
  • Тимчасово змінені об’єкти (наприклад, для тестів).

5. Взаємодія із зовнішнім класом

Якщо анонімний клас оголошено всередині нестатичного методу або блоку зовнішнього класу, він може звертатися до полів і методів цього зовнішнього класу (зокрема приватних!).

public class Outer {
    private String secret = "Секретний текст";

    // Базовий клас
    class Printer {
        public void print() {
            System.out.println("Звичайне виведення");
        }
    }

    public void revealSecret() {
        Printer p = new Printer() {
            @Override
            public void print() {
                System.out.println("Доступ до приватного: " + secret);
            }
        };
        p.print();
    }

    public static void main(String[] args) {
        new Outer().revealSecret();
    }
}

6. Типові помилки під час роботи з анонімними класами

Помилка № 1: спроба змінити змінну з навколишнього методу.
Якщо ви оголосили змінну поза анонімним класом і намагаєтеся її змінити після використання всередині анонімного класу — компілятор видасть помилку. Змінна має бути final або effectively final (не змінюватися після ініціалізації).

Помилка № 2: надто великий анонімний клас.
Якщо анонімний клас розрісся до десятків рядків і містить кілька методів — це сигнал, що варто винести його в окремий іменований клас. Інакше код стане нечитабельним.

Помилка № 3: спроба використовувати статичні методи або поля.
В анонімному класі не можна оголошувати статичні методи або поля (окрім констант). Якщо це вкрай потрібно — це привід зробити звичайний вкладений клас.

Помилка № 4: забули про область видимості.
Анонімний клас видимий лише в тому місці, де його оголошено, і не має імені. Якщо потрібне багаторазове використання — оголосіть звичайний клас.

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