JavaRush /Курсы /Java Multithreading /Внутренние анонимные классы, примеры

Внутренние анонимные классы, примеры

Java Multithreading
3 уровень , 7 лекция
Открыта
Внутренние анонимные классы, примеры - 1

— Привет, Амиго!

— Так здоровались уже, Элли!

— Так, не спорь с тетей. В 31 веке принято здороваться опять, если не видел человека более получаса. Так что не возникай!

Так вот, новая интересная тема – размножение роботов!

— О_О.

— Шучу, новая тема – анонимные вложенные классы.

Иногда в Java встречается ситуация, когда нужно унаследовать класс от нескольких классов. Т.к. множественное наследование классов в Java запрещено, эту проблему решают с помощью внутренних классов: в нужном нам классе мы объявляем внутренний класс и наследуем его от требуемого класса. Пример:

Пример внутреннего класса, унаследованного от Thread
class Tiger extends Cat
{
 public void tigerRun()
 {
  .....
 }

 public void startTiger()
 {
  TigerThread thread = new TigerThread();
  thread.start();
 }

 class TigerThread extends Thread
 {
  public void run()
  {
   tigerRun();
  } 
 }
}

Давай разберем этот пример:

Нам нужен класс, унаследованный от Thread, чтобы переопределить у него метод run.

Для этого внутри класса Tiger мы объявили внутренней класс TigerThread, который унаследовали от Thread и у которого переопределили метод run.

Для нашего удобства в классе Tiger мы объявили два метода (аналоги методов run и start класса Thread) –методы tigerRun&startTiger.

В методе startTiger мы создаем объект типа TigerThread и вызываем у него метод start().

При этом Java-машина создаст новую нить, и эта нить начнет работу с вызова метода run, класса TigerThread.

А этот метод в свою очередь вызовет наш метод run – метод tigerRun.

— С нитями я уже дело имел, так что вроде не очень сложно.

А обязательно называть методы tigerRun и startTiger?

— Нет, можно было назвать run и start, но я хотела дополнительно показать, что мы не наследуемся от Thread, к тому же ты мог сильнее запутаться в моем пояснении.

— Ок. Тогда все вроде понятно. Только при вызове метода startTiger второй раз мы создадим еще один класс Thread и запустим его. Не получится ли что у нас «один тигр будет бегать в двух различных нитях»?

— Ну ты и глазастый. Согласна, это не хорошо. Тогда давай перепишем код так:

Код
class Tiger extends Cat
{
 public void tigerRun()
 {
  .....
 }

 public void startTiger()
 {
  thread.start();
 }

 private TigerThread thread = new TigerThread();

 private class TigerThread extends Thread
 {
  public void run()
  {
   tigerRun();
  } 
 }
}

— Не то, чтобы отлично. Два раза все равно вызывать такой метод нельзя. Но в этот раз мы хотя бы не будем создавать вторую нить и делать вид, что все хорошо.

— Правильно, запустил второй раз, тигра – получи Exception.

— Я уже лучше тебя вижу все ошибки, Элли!

Да, ты молодец. Тогда перейдём к анонимным внутренним классам.

Обрати внимание на несколько аспектов вышеописанного кода:

1) Мы унаследовались от класса Thread, но фактически не дописали туда никакого кода. Нам скорее пришлось унаследоваться, а не «мы унаследовались с целью расширить класс Thread»

2) Будет создан всего один объект класса TigerThread.

Т.е. с целью переопределить один метод и создать один объект, мы написали целую кучу кода.

Помнишь, как я рассказывала про появление конструкторов?

До изобретения После изобретения
TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 } 
}
Thread thread = new Thread()
{
 public void run()
 {
  tigerRun();
 } 
};

— Вижу, что код стал компактнее, но не совсем понимаю, что произошло.

— Мы можем объединить в одном месте четыре вещи:

1) объявление класса-наследника

2) переопределение метода

3) объявление переменной

4) создание объекта класса-наследника.

Фактически мы объединяем вместе две операции – объявление класса-наследника и создание его объекта:

Без анонимного класса С использованием анонимного класса
Cat tiger = new Tiger();

class Tiger extends Cat
{
}
Cat tiger = new Cat()
{
};

Еще раз разбираем синтаксис:

Объявление переменной типа Thread
Thread thread = new Thread();
Объявление переменной типа «анонимный класс-наследник Thread»
Thread thread = new Thread()
{

};

Обрати внимание – мы не просто объявляем новый класс – мы создаем переменную – в конце ставится точка с запятой!

— А если мы хотим переопределить метод run, то нужно писать так:

Объявление переменной типа Thread
Thread thread = new Thread()
{
 public void run()
  {
   System.out.println("new run-method");
  }
};

— Быстро схватываешь – молодец!

— Спасибо. А если мне нужны еще методы, которых нет у класса Thread?

— Ты можешь их дописать.

Это же полноценный внутренний класс, хоть и анонимный:

Код на Java Описание

 Thread thread = new Thread() { public void run() { printHi(); } public void printHi() { System.out.println("Hi!"); } }; 
       
Красным цветом отмечен код создания переменной.

Зеленым – создания объекта.

Синим – код анонимного класса-наследника.

— Полноценный внутренний класс?

Т.е. я могу и переменные внешнего класса использовать?

— Да, конечно можешь.

— А в конструктор я могу что-то передавать?

— Да, но только параметры конструктора базового класса:

Класс Объект анонимного внутреннего класса
class Cat
{
 int x, y;
 Cat(int x, int y)
 {
  this.x = x;
  thix.y = y;
 }
}
Cat cat = new Cat(3, 4)
{
  public void print()
  {
   System.out.println(x+" "+y);
  }
};

Мы не можем добавить свои параметры в чужой конструктор. Зато можем использовать переменные внешнего класса – это достаточно сильно компенсирует этот недостаток.

— А если мне все-таки очень нужно добавить в конструктор еще параметры?

— Тогда объяви обычный не анонимный внутренний класс и используй его.

— Действительно, я чуть не забыл об этом.

А если я объявлю статическую переменную, тогда анонимный класс будет вложенным, а не внутренним — т.е. без ссылки на внешний класс?

— Нет. Будет именно анонимный внутренний класс. Смотри примеры:

С анонимным классом Без анонимного класса
Thread thread = new Thread()
{
  public void run()
  {
   tigerRun();
  } 
};
TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 } 
}
static Thread thread = new Thread()
{
  public void run()
  {
   tigerRun();
  } 
};
static TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 } 
}

— Ясно. Статической становится только переменная, но не класс.

— Ага.

На самом деле, в процессе компиляции Компилятор создает внутренние классы для всех анонимных внутренних классов. Такие классы обычно получают имена «1», «2», «3», и т.д.

Комментарии (187)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Victor Уровень 36
9 сентября 2025
как-то всё мутно
I'll kick them all Уровень 5
19 августа 2025
Столько есть примеров нормального использования анонимного класса, нет, надо показать через иерархию, проблема в которой решается через class Tiger extends Cat implements Runnable Ну камон, хотя б анонимний компаратор показали б.
Ioanna Polyak Уровень 31
8 июля 2025
🙂
Anemon Уровень 13 Expert
26 ноября 2024
Вот тут не понимаю, почему настолько низкая оценка. Некоторые прошлые лекции были куда хуже хд
Алексей Уровень 7
10 ноября 2024
раздражает уже Амиго. Все ему видите ли понятно...
FolFix Уровень 38
4 ноября 2024
Годная вещь
SomeBody098 Уровень 51
21 июля 2024
🙂
Long_byte Уровень 43
15 мая 2024
в оcнове лямбды лежит анонимные классы
Lou Domico Уровень 35
21 февраля 2024
Мы можем создать свои методы внутри анонимного класса, но использовать их будет нельзя, т.к. переменная родителя не в курсе о методах наследника. Единственный способ их использовать - засунуть внутрь переопределённого метода родителя. Ну, или я просто не знаю как обратиться к "переменной" анонимного класса, которой нет. ¯\_(ツ)_/¯
dlebedev Уровень 27
1 июля 2024
Вот так можно обратиться, но не нужно так делать.

public class Hello {

    public static void main(String[] args) {
        Hello h = new Hello();
        try {
            h.example.getClass().getMethod("call").invoke(h.example);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Определение класса Example:
    // public class Example {
    //
    // }
    Example example = new Example() {
        public void call() {
            System.out.println("Hello");
        }
    };
}
Андрей Уровень 37 Expert
4 июля 2024
Начиная с 10 версии джавы, используй тип данных var:

 // Определение класса Example:
     public class Example {
    
     }
.....
    var example = new Example() {
        public final String hello = "Hello";
        public void call() {
            System.out.println(hello) ;
        }
    };
  example.call();
  System.out.println(example.hello) ; 
....
Ещё можно выполнить функцию сразу после создания класса

...
    new Example() {
        public void call() {
            System.out.println("Hello");
        }
    }.call();
...
Anemon Уровень 13 Expert
26 ноября 2024
Обычно, насколько я понимаю, методы именно переопределяются, а не добавляются новые. В этом супер-удобство такого способа, он очень быстрый и лёгкий. А если создаешь именно новый метод, тут, имхо, проще создать его через Runnable, или куча других способов есть. Хотя кто его знает, учимся же только.
Sergey Уровень 51
1 февраля 2024
Это чувство, когда Амиго стал умнее меня...
26 февраля 2024
🤣🤣🤣