— Привіт, Аміго! Давай розберемо нову тему – анонімні вкладені класи.

Іноді в 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», тощо.