Анонимные классы
Разработаем новую реализацию MobilePhone , у которой будет экран c электронными чернилами — EInkPhone . У такого экрана нет понятия «включён»/«выключен», и его реализация будет крайне простой.
Кроме именованных классов есть ещё анонимные. У них нет явного имени.
public class EInkPhone implements MobilePhone < private final Screen screen = new Screen() < // тело анонимного класса @Override public boolean isBackLightEnabled() < return false; >>; . @Override public Screen getScreen() < return screen; >@Override public void unlock() < // экран и без того «включён» >>
Анонимный класс может реализовывать только один интерфейс либо наследовать один класс.
Технически анонимные классы являются разновидностью внутренних, т. е. им доступны поля внешнего класса.
Если анонимный класс объявлен в теле метода, ему доступны все финальные локальные переменные. Это называется захватом области видимости (visibility scope capturing).
public static void main(String[] args) < final String local = "some text here"; Object obj = new Object() < @Override public String toString() < return "captured: " + local; >> System.out.println(obj); // captured: some text here >
Анонимные внутренние классы
В языке Java есть возможность создавать анонимные классы, то есть, классы без имени. На этом занятии мы увидим как они объявляются и для чего используются.
Итак, анонимные внутренние классы всегда используются как расширения обычных классов или интерфейсов. То есть, изначально нам нужно объявить какой-нибудь обычный класс (или интерфейс), например:
а, затем, в процессе создания экземпляра этого класса, мы можем расширить его функционал через анонимный внутренний класс. Это делается по следующему синтаксису:
Смотрите, вот эти фигурные скобки, что идут после создания экземпляра класса (или интерфейса) и есть объявление внутреннего анонимного класса, так как у него мы явно не указываем никакого имени. Хотя имя у него все-таки есть. Виртуальная машина Java автоматически присвоит ему имя по правилу
где X – целое число (порядковый номер анонимного класса).
То есть, в нашем примере с классом Button, мы можем объявить вложенный анонимный класс, следующим образом:
Button btn = new Button() { };
Правда функционала здесь пока еще никакого нет. Давайте его добавим, а заодно увидим для чего все это нужно. Предположим, что при нажатии на кнопку должен вызываться метод click() и этот метод мы пропишем внутри класса Button:
class Button { public void click() { System.out.println("Нажатие на кнопку"); } }
Но далее, по программе собираемся использовать этот класс кнопки для разных задач: открытия файла, сохранения проекта, копирования данных и т.п. Используя наши текущие знания, мы, конечно же создали бы несколько дочерних классов и переопределили в них метод click():
class ButtonOpen extends Button { public void click() { System.out.println("Открытие файла"); } }
И так для каждой операции. В результате, у нас в программе появится большое число разных дочерних классов и их количество будет постоянно расти по мере развития проекта. Это не очень удобно. Вот здесь, как раз, нам на помощь и приходят внутренние анонимные классы. Вместо создания дочерних классов, мы при создании объекта Button, сразу переопределим его метод click с помощью анонимного класса:
Button btnCopy = new Button() { public void click() { System.out.println("Копирование данных"); } };
И, затем, вызывая этот метод:
btnCopy.click();
в консоли увидим сообщение:
То есть, с помощью внутреннего анонимного класса мы «на лету» переопределили его метод click() и задали нужное нам действие. Никаких дочерних классов создавать не потребовалось. Мало того, текст программы стал более прозрачным и ясным. Программист непосредственно в момент создания объекта видит, что именно он будет выполнять при нажатии на кнопку. Все это и обусловливает удобство вложенных анонимных классов.
Давайте теперь внимательнее посмотрим, как работает эта конструкция. Проведем такой маленький эксперимент. Если в анонимный класс добавить некий метод:
Button btnCopy = new Button() { public void click() { System.out.println("Копирование данных"); } public void doSome() { System.out.println("do something. "); } };
А, затем, вызвать его через ссылку btnCopy:
btnCopy.doSome();
то возникнет ошибка. Спрашивается: почему мы можем обращаться к методу click(), но не можем вызывать метод doSome()? Я думаю, вы уже догадались почему. Все дело в том, что анонимный класс фактически наследуется от базового класса Button. В этом смысл слова «внутренний» анонимный класс. Поэтому, мы можем вызывать метод click() благодаря механизму динамической диспетчеризации методов, так как такой же метод click() объявлен в классе Button. А вот «добраться» до метода doSome() уже нет никакой возможности. Но, если нам изменить тип ссылки btnCopy так, чтобы она имела тип анонимного вложенного класса:
то проблем с вызовом метода doSome() не возникнет, т.к. ссылка btnCopy теперь имеет тип дочернего анонимного класса (благодаря ключевому слову var, виртуальная машина Java в момент выполнения кода автоматически приводит ссылку к нужному типу данных).
Этот пример еще раз показывает, что здесь создается не объект класса Button, а объект дочернего анонимного класса.
Использование анонимных классов с интерфейсами
В нашей реализации с кнопкой есть один существенный недостаток: анонимный класс встраивается в цепочку наследования:
В результате, программист может случайно (или намеренно) переопределить методы и нарушить штатную работу объекта Button. Большую гибкость и безопасность работы можно достичь, используя интерфейсы. Например, можно определить интерфейс EventHandler, связанный с обработкой определенного типа события, в котором объявлен абстрактный метод execute(). Далее, мы можем при создании экземпляра класса Button создать класс, реализующий интерфейс EventHandler с помощью анонимного класса, в котором определим нужную нам реализацию метода execute:

На уровне языка Java все это можно реализовать так. Сначала запишем интерфейс EventHandler и класс Button:
interface EventHandler { void execute(); } class Button { EventHandler handler; Button(EventHandler handler) { this.handler = handler; } public void click() { handler.execute(); } }
А, затем, создадим кнопку в методе main():
public class Main { public static void main(String[] args) { var btnCopy = new Button(new EventHandler() { public void execute() { System.out.println("Копирование данных"); } }); btnCopy.click(); } }
Смотрите, как элегантно и красиво выглядит реализация обработки события в классе Button. Сначала, при создании экземпляра кнопки мы тут же создаем объект анонимного вложенного класса, реализующий интерфейс EventHandler. Поэтому, внутри фигурных скобок обязаны определить метод execute() с конкретной реализацией. Далее, ссылку на созданный объект анонимного класса передаем как аргумент в конструктор класса Button и сохраняем ее, используя тип интерфейса EventHandler. Нам этого вполне достаточно, так как затем, в методе click() вызываем переопределенный метод execute() в анонимном классе через ссылку handler. Все, таким образом, мы отделили обработку события от реализации класса Button и, кроме того, можем разными интерфейсами описывать разные типы событий. Это обеспечивает дополнительную гибкость программного кода.
Вот так анонимные вложенные классы позволяют «на лету» создавать нужные нам объекты на основе других классов или интерфейсов.
Видео по теме

#11 Концепция объектно-ориентированного программирования (ООП)

#12 Классы и создание объектов классов

#13 Конструкторы, ключевое слово this, инициализаторы

#14 Методы класса, сеттеры и геттеры, public, private, protected

#15 Пакеты, модификаторы конструкторов и классов

#16 Ключевые слова static и final

#17 Внутренние и вложенные классы

#18 Как делается наследование классов

#19 Ключевое слово super, оператор instanceof

#20 Модификаторы private и protected, переопределение методов, полиморфизм

#21 Абстрактные классы и методы

#22 Интерфейсы — объявление и применение

#23 Интерфейсы — приватные, статические и дефолтные методы, наследование интерфейсов

#24 Анонимные внутренние классы

#25 Перечисления (enum)

#26 Обобщения классов (Generics)

#27 Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

#28 Обобщенные интерфейсы, наследование обобщенных классов
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Анонимные классы
Если вы много программировали на языке Java, вы могли заметить, что можно объявить классы, вложенные в другие классы. В этой статье рассматривается один из особых типов вложенности, называемый «анонимным классом». Для начала взглянем на следующий простой пример:
static class B > // статический вложенный класс
class C > // внутренний класс
void f () class D > // локальный внутренний класс
>
void g () // анонимный класс
Base bref = new Base () void method1 () >
> ;
>
>
В этом примере есть несколько типов вложенных и внутренних классов. Вложенный класс, не объявленный статическим, называется внутренним классом. В данном коде B является вложенным классом, а C — вложенным и внутренним классом.
Главной целью данной статьи являются анонимные классы. Вы можете понять суть анонимных классов, изучив приведенный выше пример. Основная особенность — анонимный класс не имеет имени. Анонимный класс является подклассом существующего класса (в примере Base) или реализации интерфейса.
Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально. Например, в следующем фрагменте кода объявляются два различных анонимных класса:
Base bref1 = new Base () <
void method1 () <>
> ;
Base bref2 = new Base () void method1 () <>
> ;
Каждый анонимный класс объявляется внутри выражения.
Типичный пример применения
Рассмотрим типичную ситуацию, в которой вы могли бы применить анонимный класс:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class AnonDemo2 public static void main ( String args [])
// создать JFrame и добавить к нему перехватчик
// событий для обработки закрытия окна
JFrame frame = new JFrame ( «AnonDemo2» ) ;
frame.addWindowListener ( new WindowAdapter () public void windowClosing ( WindowEvent e ) System.exit ( 0 ) ;
>
>) ;
// создать JPanel и добавить к фрейму
JPanel panel = new JPanel () ;
panel.setPreferredSize ( new Dimension ( 300 , 300 )) ;
frame.getContentPane () .add ( panel ) ;
frame.pack () ;
frame.setVisible ( true ) ;
>
>
Этот пример отображает JPanel на экране. К объекту JFrame добавляется перехватчик событий, который завершает приложение при закрытии окна пользователем.
WindowsListener является интерфейсом. Класс реализации должен определить все методы, указанные в интерфейсе. WindowAdapter реализует интерфейс, используя фиктивные методы, например:
public abstract class WindowAdapter implements WindowListener,
WindowStateListener, WindowFocusListener public void windowOpened ( WindowEvent e ) >
public void windowClosing ( WindowEvent e ) >
public void windowClosed ( WindowEvent e ) >
public void windowIconified ( WindowEvent e ) >
public void windowDeiconified ( WindowEvent e ) >
public void windowActivated ( WindowEvent e ) >
public void windowDeactivated ( WindowEvent e ) >
public void windowStateChanged ( WindowEvent e ) >
public void windowGainedFocus ( WindowEvent e ) >
public void windowLostFocus ( WindowEvent e ) >
>
Демонстрационное приложение AnonDemo2 должно переопределить только один из этих методов, а именно — windowClosing. То есть, приложение наследует класс адаптера и переопределяет один метод. Подкласс используется в приложении только один раз и имеет очень простую логику. Вот почему анонимный класс является хорошим выбором в данном случае. Анонимный класс расширяет класс WindowAdapter, переопределяя один метод. WindowAdapter, в свою очередь, реализует класс WindowListener, используя фиктивные, ничего не выполняющие методы.
Сортировка списка с использованием анонимных классов
Рассмотрим другой пример. Предположим, что вы имеете список List объектов Integer, и хотите отсортировать список и в возрастающем (по умолчанию) и в убывающем порядке. Вот программа, выполняющая эту задачу:
public class AnonDemo3 public static void main ( String args [])
// создать ArrayList и добавить в него
// несколько объектов Integer
List list = new ArrayList () ;
list.add ( new Integer ( 37 )) ;
list.add ( new Integer ( — 59 )) ;
list.add ( new Integer ( 83 )) ;
// отсортировать список обычным способом (по возрастанию)
Collections.sort ( list ) ;
System.out.println ( list ) ;
// отсортировать список по убыванию,
// используя функцию, реализованную объектом
// при помощи анонимного класса
Collections.sort ( list, new Comparator () public int compare ( Object o1, Object o2 ) int a = (( Integer ) o1 ) .intValue () ;
int b = (( Integer ) o2 ) .intValue () ;
return a < b ? 1 : a == b ? 0 : - 1 ;
>
>) ;
System.out.println ( list ) ;
>
>
Программа выполняет первую сортировку очевидным способом. Для того чтобы выполнить сортировку по убыванию программа должна определить объект функции Comparator.Этот объект реализует логику сравнения для сортировки объектов Integer в убывающем порядке.
В этой программе используется анонимный класс, реализующий интерфейс java.util.Comparator. Если такой тип сортировки производится только в одном месте приложения, то имеет смысл использовать анонимный класс, но если такая сортировка нужна во многих местах, то больший смысл имеет определить класс на более высоком уровне вложенности, или статический вложенный класс,
class MyComparator implements Comparator <
.
>
и реализовать логику сортировки только один раз.
Программа отображает следующую информацию:
[ — 59 , 37 , 83 ]
[ 83 , 37 , — 59 ]
Примеры использования
Давайте рассмотрим последний пример, в котором демонстрируется несколько приемов использования анонимных классов:
class A <
int afield;
// установить значение afield
A ( int afield ) this .afield = afield;
>
// получить значение afield
int getValue () return afield;
>
>
public class AnonDemo4 static A createAnon () final int dlocal = 40 ;
// возвратить из метода f() экземпляр
// анонимного класса, порожденного из A
// вызвать конструктор суперкласса
return new A ( 10 ) int bfield = 20 ;
int cfield;
int getValue () return afield + bfield + cfield + dlocal;
>
> ;
>
public static void main ( String args []) A anonref = createAnon () ;
В этом примере метод createAnon объявляет анонимный класс и возвращает ссылку типа суперкласс (A) на экземпляр анонимного класса. Это означает, что экземпляр анонимного класса может быть использован вне объявляющего его контекста (createAnon). Далее вызывается метод getValue объектной ссылки на анонимный класс.
Вспомните, что анонимные классы не имеют имени, следовательно, они не могут иметь явные конструкторы. Но существует несколько способов преодоления такого ограничения. Когда создается экземпляр анонимного класса, например:
автоматически вызывается конструктор суперкласса.
Инициализация экземпляра анонимного класса происходит обычным путем, то есть
int bfield = 20 ;
<
cfield = 30 ;
>
работают как обычно. Эти механизмы могут использоваться для выполнения части работы, которая в нормальных случаях выполняется в конструкторе.
Пример AnonDemo4 имеет одну необычную особенность. Переменная dlocal объявляется как финальная. Если убрать ключевое слово final из объявления — компилятор выдаст ошибку. Почему? Потому что есть возможность, и это демонстрирует данная программа, обратиться к анонимному объекту вне контекста, в котором он был объявлен. Если сделать такое обращение, какое значение будет иметь переменная dlocal, учитывая, что это локальная переменная, объявленная в методе createAnon? Это классический вопрос программирования, возникающий при обращении к неверному фрейму стека.
Для решения этой проблемы локальная переменная должна быть финальной, то есть связанной с определенным значением, которое может быть использовано вместо самой переменной (dlocal). То есть, вместо использования «dlocal» используется значение «40».
Анонимные классы очень полезны, но нельзя их переоценивать. Какой-либо другой тип класса может быть более хорошим выбором при использовании в приложении более чем одного анонимного класса с одинаковой логикой, либо в тех случаях, когда логика такого класса является сложной, либо при наличии глубокой вложенности классов. Кроме того, объявления анонимных классов трудно читаемы, поэтому вы должны делать их простыми.
Ссылки
Дополнительная информация об анонимных классах находится в разделе 5.4 «Анонимные внутренние классы» учебника «Язык программирования Java(tm)» Арнольда, Гослинга и Холмса (Arnold, Gosling, Holmes).
Также обратитесь к разделу «Преимущество статических классов-членов над нестатическими» в книге «Эффективное руководство по языку программирования Java» Джошуа Блоха (Joshua Bloch).
A может Вас также заинтересует что-нибудь из этого:
- Разное → Теория и практика Java: Динамическая компиляция и измерение производительности
- Java Standard Edition → Блокировки
- Java Standard Edition → Производительность операций ввода/вывода в Java
- Java сниппеты → Методы для работы с переменным количеством аргументов
- Разное → C# глазами Java программиста
- Java сниппеты → Использование readResolve
Как создать анонимный класс java
В Java анонимный класс — это класс без имени, который определяется непосредственно внутри другого класса или метода. Он обычно используется для реализации интерфейсов или классов-родителей с небольшим количеством методов, и не требует создания отдельного класса для реализации этих методов.
Вот пример создания анонимного класса:
public class Main public static void main(String[] args) Runnable runnable = new Runnable() @Override public void run() System.out.println("Этот код выполняется в анонимном классе"); > >; Thread thread = new Thread(runnable); thread.start(); > >
В этом примере мы
- создаем объект типа Runnable с помощью анонимного класса, который реализует метод run()
- затем мы создаем объект Thread , передавая ему объект runnable в качестве параметра конструктора, и запускаем поток. Обратите внимание, что анонимный класс определяется непосредственно внутри вызова конструктора Thread , и не имеет собственного имени.
Важно отметить, что анонимный класс может использоваться только один раз и не может быть повторно использован в других местах программы. Если вам нужно использовать реализацию интерфейса или класса-родителя несколько раз, лучше создать отдельный класс для этого.
