Внутренние анонимные классы, примеры - 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», и т.д.