Анонимни класове в Java

1. Въведение

В този урок ще разгледаме анонимни класове в Java.

Ще опишем как можем да декларираме и създаваме екземпляри от тях. Също така ще обсъдим накратко техните свойства и ограничения.

2. Анонимна декларация за клас

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

Можем или да разширим съществуващ клас, или да внедрим интерфейс.

2.1. Разширете клас

Когато създаваме екземпляр на анонимен клас от съществуващ, използваме следния синтаксис:

В скобите посочваме параметрите, които се изискват от конструктора на класа, който разширяваме:

new Book("Design Patterns") { @Override public String description() { return "Famous GoF book."; } }

Естествено, ако конструкторът на родителски клас не приема аргументи, трябва да оставим скобите празни.

2.2. Внедряване на интерфейс

Можем да създадем екземпляр и на анонимен клас от интерфейс:

Очевидно интерфейсите на Java нямат конструктори, така че скобите винаги остават празни. Това е единственият начин да го направим, за да приложим методите на интерфейса:

new Runnable() { @Override public void run() { ... } }

След като създадем екземпляр на анонимен клас, можем да присвоим този екземпляр на променлива, за да можем да я препращаме някъде по-късно.

Можем да направим това, като използваме стандартния синтаксис за изрази на Java:

Runnable action = new Runnable() { @Override public void run() { ... } };

Както вече споменахме, анонимната декларация на клас е израз, следователно трябва да е част от изявление . Това обяснява защо сме сложили точка и запетая в края на изявлението.

Очевидно можем да избегнем присвояването на екземпляра на променлива, ако създадем този инстанция вграден:

List actions = new ArrayList(); actions.add(new Runnable() { @Override public void run() { ... } });

Трябва да използваме този синтаксис с голямо внимание, тъй като може лесно да пострада четливостта на кода, особено когато изпълнението на метода run () отнема много място.

3. Анонимни свойства на класа

Има някои особености при използването на анонимни класове по отношение на обичайните класове от най-високо ниво. Тук накратко засягаме най-практичните въпроси. За най-точна и актуална информация винаги можем да разгледаме спецификацията на езика Java.

3.1. Конструктор

Синтаксисът на анонимните класове не ни позволява да ги накараме да реализират множество интерфейси. По време на строителството може да съществува точно един екземпляр на анонимен клас . Следователно те никога не могат да бъдат абстрактни. Тъй като нямат име, не можем да ги удължим. По същата причина анонимните класове не могат да имат изрично декларирани конструктори.

Всъщност липсата на конструктор не представлява проблем за нас поради следните причини:

  1. създаваме анонимни екземпляри на класа в същия момент, в който ги декларираме
  2. от анонимни екземпляри на клас можем да осъществим достъп до локални променливи и затваряне на членовете на класа

3.2. Статични членове

Анонимните класове не могат да имат статични членове, с изключение на тези, които са постоянни.

Например, това няма да се компилира:

new Runnable() { static final int x = 0; static int y = 0; // compilation error! @Override public void run() {...} };

Вместо това ще получим следната грешка:

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

3.3. Обхват на променливите

Анонимните класове улавят локални променливи, които са в обхвата на блока, в който сме декларирали класа:

int count = 1; Runnable action = new Runnable() { @Override public void run() { System.out.println("Runnable with captured variables: " + count); } }; 

Както виждаме, броят и действието на локалните променливи се дефинират в един и същ блок. Поради тази причина можем да получим достъп до count от декларацията на класа.

Имайте предвид, че за да можете да използвате локални променливи, те трябва да бъдат ефективно окончателни. От JDK 8 вече не се изисква да декларираме променливи с ключовата дума final . Въпреки това тези променливи трябва да са окончателни . В противен случай получаваме грешка при компилация:

[ERROR] local variables referenced from an inner class must be final or effectively final

За да може компилаторът да реши, че променливата всъщност е неизменна, в кода трябва да има само едно място, на което да й присвоим стойност. Можем да намерим повече информация за ефективно крайните променливи в нашата статия „Защо местните променливи, използвани в ламбда, трябва да бъдат окончателни или ефективно окончателни?“

Нека само споменем, че както всеки вътрешен клас, анонимният клас може да има достъп до всички членове на своя затварящ клас .

4. Анонимни случаи на използване на клас

Може да има голямо разнообразие от приложения на анонимни класове. Нека разгледаме някои възможни случаи на използване.

4.1. Класова йерархия и капсулиране

We should use inner classes in general use cases and anonymous ones in very specific ones in order to achieve a cleaner hierarchy of classes in our application. When using inner classes, we may achieve a finer encapsulation of the enclosing class's data. If we define the inner class functionality in a top-level class, then the enclosing class should have public or package visibility of some of its members. Naturally, there are situations when it is not very appreciated or even accepted.

4.2. Cleaner Project Structure

We usually use anonymous classes when we have to modify on the fly the implementation of methods of some classes. In this case, we can avoid adding new *.java files to the project in order to define top-level classes. This is especially true if that top-level class would be used just one time.

4.3. UI Event Listeners

In applications with a graphical interface, the most common use case of anonymous classes is to create various event listeners. For example, in the following snippet:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ... } }

we create an instance of an anonymous class that implements interface ActionListener. Its actionPerformed method gets triggered when a user clicks the button.

Since Java 8, lambda expressions seem to be a more preferred way though.

5. General Picture

Анонимните класове, които разгледахме по-горе, са само частен случай на вложени класове. Като цяло, вложен клас е клас, който е деклариран в друг клас или интерфейс :

Разглеждайки диаграмата, виждаме, че анонимните класове заедно с локалните и нестатичните членове формират така наречените вътрешни класове . Заедно със статичните класове членове те формират вложените класове.

6. Заключение

В тази статия разгледахме различни аспекти на анонимните класове на Java. Описахме и обща йерархия на вложени класове.

Както винаги, пълният код е достъпен в нашето хранилище на GitHub.