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
Задача
JAVA 25 SELF, 48 уровень, 4 лекция
Недоступна
Цензура в цифровом зоопарке 🦒
Цензура в цифровом зоопарке 🦒
1
Задача
JAVA 25 SELF, 48 уровень, 4 лекция
Недоступна
Голоса внутреннего мира 🎭
Голоса внутреннего мира 🎭
1
Опрос
Лямбда-выражения, 48 уровень, 4 лекция
Недоступен
Лямбда-выражения
Лямбда-выражения
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ