
— Привет, Амиго!
— Так здоровались уже, Элли!
— Так, не спорь с тетей. В 31 веке принято здороваться опять, если не видел человека более получаса. Так что не возникай!
Так вот, новая интересная тема – размножение роботов!
— О_О.
— Шучу, новая тема – анонимные вложенные классы.
Иногда в Java встречается ситуация, когда нужно унаследовать класс от нескольких классов. Т.к. множественное наследование классов в Java запрещено, эту проблему решают с помощью внутренних классов: в нужном нам классе мы объявляем внутренний класс и наследуем его от требуемого класса. Пример:
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.
Т.е. с целью переопределить один метод и создать один объект, мы написали целую кучу кода.
Помнишь, как я рассказывала про появление конструкторов?
До изобретения | После изобретения |
---|---|
|
|
— Вижу, что код стал компактнее, но не совсем понимаю, что произошло.
— Мы можем объединить в одном месте четыре вещи:
1) объявление класса-наследника
2) переопределение метода
3) объявление переменной
4) создание объекта класса-наследника.
Фактически мы объединяем вместе две операции – объявление класса-наследника и создание его объекта:
Без анонимного класса | С использованием анонимного класса |
---|---|
|
|
Еще раз разбираем синтаксис:
Thread thread = new Thread();
Thread thread = new Thread()
{
};
Обрати внимание – мы не просто объявляем новый класс – мы создаем переменную – в конце ставится точка с запятой!
— А если мы хотим переопределить метод run, то нужно писать так:
Thread thread = new Thread()
{
public void run()
{
System.out.println("new run-method");
}
};
— Быстро схватываешь – молодец!
— Спасибо. А если мне нужны еще методы, которых нет у класса Thread?
— Ты можешь их дописать.
Это же полноценный внутренний класс, хоть и анонимный:
Код на Java | Описание |
---|---|
|
Красным цветом отмечен код создания переменной.
Зеленым – создания объекта. Синим – код анонимного класса-наследника. |
— Полноценный внутренний класс?
Т.е. я могу и переменные внешнего класса использовать?
— Да, конечно можешь.
— А в конструктор я могу что-то передавать?
— Да, но только параметры конструктора базового класса:
Класс | Объект анонимного внутреннего класса |
---|---|
|
|
Мы не можем добавить свои параметры в чужой конструктор. Зато можем использовать переменные внешнего класса – это достаточно сильно компенсирует этот недостаток.
— А если мне все-таки очень нужно добавить в конструктор еще параметры?
— Тогда объяви обычный не анонимный внутренний класс и используй его.
— Действительно, я чуть не забыл об этом.
А если я объявлю статическую переменную, тогда анонимный класс будет вложенным, а не внутренним — т.е. без ссылки на внешний класс?
— Нет. Будет именно анонимный внутренний класс. Смотри примеры:
С анонимным классом | Без анонимного класса |
---|---|
|
|
|
|
— Ясно. Статической становится только переменная, но не класс.
— Ага.
На самом деле, в процессе компиляции Компилятор создает внутренние классы для всех анонимных внутренних классов. Такие классы обычно получают имена «1», «2», «3», и т.д.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ