java что такое анонимный класс
Анонимные внутренние классы
В языке Java есть возможность создавать анонимные классы, то есть, классы без имени. На этом занятии мы увидим как они объявляются и для чего используются.
Итак, анонимные внутренние классы всегда используются как расширения обычных классов или интерфейсов. То есть, изначально нам нужно объявить какой-нибудь обычный класс (или интерфейс), например:
а, затем, в процессе создания экземпляра этого класса, мы можем расширить его функционал через анонимный внутренний класс. Это делается по следующему синтаксису:
btn = new () <
// данные и методы
// анонимного внутреннего класса
>
Смотрите, вот эти фигурные скобки, что идут после создания экземпляра класса (или интерфейса) и есть объявление внутреннего анонимного класса, так как у него мы явно не указываем никакого имени. Хотя имя у него все-таки есть. Виртуальная машина Java автоматически присвоит ему имя по правилу
где X – целое число (порядковый номер анонимного класса).
То есть, в нашем примере с классом Button, мы можем объявить вложенный анонимный класс, следующим образом:
Правда функционала здесь пока еще никакого нет. Давайте его добавим, а заодно увидим для чего все это нужно. Предположим, что при нажатии на кнопку должен вызываться метод click() и этот метод мы пропишем внутри класса Button:
Но далее, по программе собираемся использовать этот класс кнопки для разных задач: открытия файла, сохранения проекта, копирования данных и т.п. Используя наши текущие знания, мы, конечно же создали бы несколько дочерних классов и переопределили в них метод click():
И так для каждой операции. В результате, у нас в программе появится большое число разных дочерних классов и их количество будет постоянно расти по мере развития проекта. Это не очень удобно. Вот здесь, как раз, нам на помощь и приходят внутренние анонимные классы. Вместо создания дочерних классов, мы при создании объекта Button, сразу переопределим его метод click с помощью анонимного класса:
И, затем, вызывая этот метод:
в консоли увидим сообщение:
То есть, с помощью внутреннего анонимного класса мы «на лету» переопределили его метод click() и задали нужное нам действие. Никаких дочерних классов создавать не потребовалось. Мало того, текст программы стал более прозрачным и ясным. Программист непосредственно в момент создания объекта видит, что именно он будет выполнять при нажатии на кнопку. Все это и обусловливает удобство вложенных анонимных классов.
Давайте теперь внимательнее посмотрим, как работает эта конструкция. Проведем такой маленький эксперимент. Если в анонимный класс добавить некий метод:
А, затем, вызвать его через ссылку btnCopy:
то возникнет ошибка. Спрашивается: почему мы можем обращаться к методу click(), но не можем вызывать метод doSome()? Я думаю, вы уже догадались почему. Все дело в том, что анонимный класс фактически наследуется от базового класса Button. В этом смысл слова «внутренний» анонимный класс. Поэтому, мы можем вызывать метод click() благодаря механизму динамической диспетчеризации методов, так как такой же метод click() объявлен в классе Button. А вот «добраться» до метода doSome() уже нет никакой возможности. Но, если нам изменить тип ссылки btnCopy так, чтобы она имела тип анонимного вложенного класса:
то проблем с вызовом метода doSome() не возникнет, т.к. ссылка btnCopy теперь имеет тип дочернего анонимного класса (благодаря ключевому слову var, виртуальная машина Java в момент выполнения кода автоматически приводит ссылку к нужному типу данных).
Этот пример еще раз показывает, что здесь создается не объект класса Button, а объект дочернего анонимного класса.
Использование анонимных классов с интерфейсами
В нашей реализации с кнопкой есть один существенный недостаток: анонимный класс встраивается в цепочку наследования:
В результате, программист может случайно (или намеренно) переопределить методы и нарушить штатную работу объекта Button. Большую гибкость и безопасность работы можно достичь, используя интерфейсы. Например, можно определить интерфейс EventHandler, связанный с обработкой определенного типа события, в котором объявлен абстрактный метод execute(). Далее, мы можем при создании экземпляра класса Button создать класс, реализующий интерфейс EventHandler с помощью анонимного класса, в котором определим нужную нам реализацию метода execute:
На уровне языка Java все это можно реализовать так. Сначала запишем интерфейс EventHandler и класс Button:
А, затем, создадим кнопку в методе main():
Смотрите, как элегантно и красиво выглядит реализация обработки события в классе 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 Абстрактные классы и методы
#24 Анонимные внутренние классы
#25 Перечисления (enum)
#26 Обобщения классов (Generics)
#27 Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы
#28 Обобщенные интерфейсы, наследование обобщенных классов
© 2021 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Анонимные классы
Содержание
Введение
Если вы много программировали на языке Java, вы могли заметить, что можно объявить классы, вложенные в другие классы. В этой статье рассматривается один из особых типов вложенности, называемый «анонимным классом». Для начала взглянем на следующий простой пример:
static class B <
> // статический вложенный класс
class C <
> // внутренний класс
void f () <
class D <
> // локальный внутренний класс
>
void g () <
// анонимный класс
Base bref = new Base () <
void method1 () <
>
> ;
>
>
Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально. Например, в следующем фрагменте кода объявляются два различных анонимных класса:
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 и добавить к фрейму
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 ) <
>
>
Сортировка списка с использованием анонимных классов
Рассмотрим другой пример. Предположим, что вы имеете список List объектов Integer, и хотите отсортировать список и в возрастающем (по умолчанию) и в убывающем порядке. Вот программа, выполняющая эту задачу:
public class AnonDemo3 <
public static void main ( String args []) <
// создать ArrayList и добавить в него
// несколько объектов Integer
// отсортировать список обычным способом (по возрастанию)
Collections.sort ( list ) ;
System.out.println ( list ) ;
// отсортировать список по убыванию,
// используя функцию, реализованную объектом
// при помощи анонимного класса
Программа выполняет первую сортировку очевидным способом. Для того чтобы выполнить сортировку по убыванию программа должна определить объект функции Comparator.Этот объект реализует логику сравнения для сортировки объектов Integer в убывающем порядке.
В этой программе используется анонимный класс, реализующий интерфейс java.util.Comparator. Если такой тип сортировки производится только в одном месте приложения, то имеет смысл использовать анонимный класс, но если такая сортировка нужна во многих местах, то больший смысл имеет определить класс на более высоком уровне вложенности, или статический вложенный класс,
и реализовать логику сортировки только один раз.
Программа отображает следующую информацию:
Примеры использования
Давайте рассмотрим последний пример, в котором демонстрируется несколько приемов использования анонимных классов:
class A <
int 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 объектной ссылки на анонимный класс.
Вспомните, что анонимные классы не имеют имени, следовательно, они не могут иметь явные конструкторы. Но существует несколько способов преодоления такого ограничения. Когда создается экземпляр анонимного класса, например:
автоматически вызывается конструктор суперкласса.
Инициализация экземпляра анонимного класса происходит обычным путем, то есть
работают как обычно. Эти механизмы могут использоваться для выполнения части работы, которая в нормальных случаях выполняется в конструкторе.
Для решения этой проблемы локальная переменная должна быть финальной, то есть связанной с определенным значением, которое может быть использовано вместо самой переменной (dlocal). То есть, вместо использования «dlocal» используется значение «40».
Анонимные классы очень полезны, но нельзя их переоценивать. Какой-либо другой тип класса может быть более хорошим выбором при использовании в приложении более чем одного анонимного класса с одинаковой логикой, либо в тех случаях, когда логика такого класса является сложной, либо при наличии глубокой вложенности классов. Кроме того, объявления анонимных классов трудно читаемы, поэтому вы должны делать их простыми.
Ссылки
Дополнительная информация об анонимных классах находится в разделе 5.4 «Анонимные внутренние классы» учебника «Язык программирования Java(tm)» Арнольда, Гослинга и Холмса (Arnold, Gosling, Holmes).
Также обратитесь к разделу «Преимущество статических классов-членов над нестатическими» в книге «Эффективное руководство по языку программирования Java» Джошуа Блоха (Joshua Bloch).
Внутренние и вложенные Java классы
В Java вложенные классы – это классы, которые определены внутри другого класса.
Цель – четко сгруппировать вложенный класс с окружающим его классом, сигнализируя о том, что эти два класса должны использоваться вместе. Или, возможно, что вложенный должен использоваться только внутри его вмещающего (владеющего) класса.
Java-разработчики часто используют внутренние классы (нестатические вложенные) – это только один из нескольких различных типов:
Вложенные считаются членами включающего их класса. Таким образом, могут быть объявлены как открытые, пакетные (без модификатора доступа), защищенные и приватные. Поэтому могут наследоваться подклассами.
Статические
Чтобы создать экземпляр класса Nested, вы должны сослаться на него, добавив к нему префикс имени внешнего класса, например:
Статический тип – это, по сути, обычный класс, который только что был вложен в другой класс. Может обращаться к переменным экземпляра включающего класса только через ссылку на его экземпляр.
Нестатические внутренние классы
Нестатические вложенные также называются внутренними и связаны с экземпляром включающего класса. Таким образом, вы должны сначала создать экземпляр окружающего класса, чтобы создать экземпляр внутреннего:
Вот как вы создаете экземпляр класса Inner:
Обратите внимание, как вы ставите new после ссылки на внешний класс, чтобы создать экземпляр внутреннего.
Нестатические классы (внутренние классы) имеют доступ к полям включающего класса, даже если они объявлены закрытыми:
Обратите внимание, как метод printText() класса Inner ссылается на поле частного текста класса Outer. Это вполне возможно. Вот как вы бы вызвали метод printText():
Shadowing
Если внутренний класс объявляет поля или методы с теми же именами, что и у поля или методов в своем включающем классе, внутренние поля или методы называются внешними полями или методами :
В приведенном выше примере класс Outer и Inner содержит поле с именем text. Когда класс Inner ссылается на текст, он ссылается на свое собственное поле. Когда Outer ссылается на текст, он также ссылается на свое собственное поле.
Теперь метод Inner.printText() будет печатать поля Inner.text и Outer.text.
Локальные
Локальные классы в Java похожи на внутренние (нестатические вложенные), которые определены внутри метода или блока контекста(<…>) внутри метода:
Характеристика локальных классов:
Анонимные
Анонимные классы – это вложенные без имени класса. Они обычно объявляются либо как подклассы существующего класса, либо как реализации некоторого интерфейса.
Определяются, когда они создаются. Вот пример, который объявляет анонимный подкласс суперкласса с именем SuperClass:
Запуск этого кода приведет к тому, что анонимный класс doIt() будет напечатан в System.out. Он расширяет SuperClass и переопределяет метод doIt().
Анонимный класс также может реализовывать интерфейс вместо расширения класса:
Как вы можете видеть, анонимные классы, реализующий интерфейс и расширяющий другой класс очень похожи.
Этот тип класса может получить доступ к членам включающего класса. Также к локальным переменным, которые объявлены как final.
Вы можете объявить поля и методы внутри такого класса, но не можете объявить конструктор. Вместо этого возможно объявить статический инициализатор:
К этому типу класса применяются те же правила дублирования, что и к внутренним.
Преимущества
Преимущества в том, что вы можете группировать классы, которые принадлежат друг другу. Возможно сделать это, поместив их в один и тот же пакет, но размещение одного класса внутри другого делает еще более сильную группировку.
Иногда они видны только включающему классу, используется только внутри и, следовательно, никогда не видны вне охватывающего класса. В других случаях вложенный виден за пределами его окружающего класса, но может использоваться только вместе с включающим.
Примером может быть класс Cache. Внутри Cache вы можете объявить класс CacheEntry, который может содержать информацию о конкретной записи в кэше (кэшированное значение, время вставки, количество обращений и т. д.).
Пользователи Cache могут никогда не увидеть CacheEntry, если им не нужно получать информацию о самом CacheEntry, а только кэшированное значение. Тем не менее, Cache может сделать CacheEntry видимым для внешнего мира, чтобы они могли получить доступ не только к кэшированному значению (например, к информации о том, когда значение было обновлено в последний раз и т. д.).
Вот два скелета реализации Cache, иллюстрирующие эти моменты:
Первый класс Cache скрывает свой вложенный класс CacheEntry, в то время как второй класс Cache предоставляет его.
Pro Java
Страницы
28 авг. 2015 г.
Внутренние классы. Часть 5 – анонимные классы.
Анонимный класс – это локальный класс без имени. Можно объявить анонимный (безымянный) класс, который может расширить (extends) другой класс или реализовать (implements) интерфейс. Объявление такого класса выполняется одновременно с созданием его объекта посредством оператора new.
Анонимные классы эффективно используются, как правило, для реализации (переопределения) нескольких методов и создания собственных методов объекта. Так же когда локальный класс используется всего один раз, можно применить синтаксис анонимного класса, который позволяет совместить определение и использование класса.
Теперь рассмотрим на практике простой пример:
Во втором же примере не все так прозрачно, так как может показаться что у класса все же есть имя External, но это не так. Класс по прежнему анонимный, так как он расширяет класс External и не является экземпляром класса External, хотя ссылка на этот анонимный класс присвоена переменной класса External, что является нормальным и обычным. Надеюсь что опять ни кто не запутался 🙂
Анонимные классы, если они созданы как расширение супер класса могут ссылаться на члены своих супер классов. Код и вывод программы подтверждают это.
Первую строку выводит метод outPrint(), другие – extPrint().
Теперь небольшой пример на то, что объявление анонимного класса представляет собой выражение. То есть мы можем использовать его как часть другого выражения, что показано на примере слева подсвеченной строкой. Так же обратите внимание как мы получаем доступ к анонимному классу в данном примере.
Вывод у программы следующий:
Как видите, поскольку анонимный класс не имеет имени, то компилятор использует номера в качестве идентификаторов для таких классов. На скриншоте показано как выглядят в откомпилированном виде классы нашей программы.
Так как анонимные классы представляют один из типов локальных классов, анонимные классы и локальные классы несут одинаковые ограничения.
Анонимные классы, так же как и локальные, имеют доступ к локальным переменным своего блока кода которые объявлены как final или они должны быть effectively final .
Анонимные классы имеют доступ ко всем членам своего вешнего класса.
Если в анонимном классе объявлена переменная с таким же именем как и в окружающем классе, то она затеняет переменную окружающего класса.
Анонимный класс не может определять статические поля, методы или классы, кроме констант static final. Интерфейс не может быть объявлен анонимно, потому что нет способа реализовать интерфейс без имени. Так же как и локальные классы, анонимные классы не могут быть public, private, protected или static.
В анонимном классе вы не можете объявить статические инициализационные блоки.
В анонимном классе вы не можете объявить интерфейс.
В анонимном классе вы можете объявить:
Ну и теперь рассмотрим примеры всех вышеприведенных утверждений.
Начнем пожалуй с этого 🙂 то есть с .this. В Принципе это можно было еще на первом примере показать, но чет запамятовал. В общем первый пример мутировал в текущий. Я добавил строку str в класс Outer и затем обратился к ней из анонимного класса. Эти две строки подсвечены желтым.
Теперь у программы такой вывод:
В общем тут нет ни чего не обычного, так как анонимные классы это те же внутренние классы, но с некоторыми ограничениями которые мы уже озвучили чуть выше.
А теперь попробуем смоделировать конструктор анонимного класса 🙂
Чтобы смоделировать конструктор в анонимном классе нам будет необходим класс от которого будет наследоваться наш анонимный класс. С анонимным классом имплементирующем интерфейс такой номер не прокатит, так как у интерфейсов нет конструкторов.
Нам так же необходимо чтобы у родительского класса был конструктор. И тогда, как уже говорилось, все аргументы которые будут указаны в круглых скобках при создании анонимного класса будут передаваться конструктору родительского класса, но так же будут доступны и в анонимном. Причем даже если в родительском классе эти аргументы используются только в конструкторе и ни где более, то есть не сохраняются в полях родительского класса, эти аргументы все равно будут доступны в анонимном классе. Но они должны быть переданы в конструктор с модификатором final или быть effectively final .
В данном примере так же видно, что анонимный класс имеет доступ к private полям внешнего класса, а аргумент i переданный в конструктор супер класса доступен в инициализаторе и методах анонимного класса и этот аргумент можно использовать для инициализации полей анонимного класса, но саму переменную i изменять нельзя.
Вывод программы представлен ниже:
Как видно из вывода программы аргумент i переданный в конструктор суперкласса Base доступен как в конструкторе суперкласса, так и в инициализаторе и методах анонимного класса, который в данном случае является и наследником класса Base и так же внутренним его классом. Именно поэтому ему доступно private static поле i. Я умышленно дал одинаковые названия аргументу и полю, чтобы продемонстрировать области видимости переменных и полей.
И еще один пример на тему эмулирования конструкторов в анонимных классах:
В это программе не используются статические методы и поля для того чтобы продемонстрировать доступ к полям экземпляров в классах Base и External. По существу у нас есть два анонимных класса. Анонимный класс в классе Base является и вложенным в него и его же наследником, а в классе External анонимный класс является наследником класса Base и внутренним для класса External. Именно по этому в первом случае анонимный класс имеет доступ к private полю str класса Base, а во втором не имеет и использует для доступа к нему унаследованный метод getStr(). Собственно из вывода программы видно как она работает 🙂
Я немного изменил предыдущий пример, чтобы продемонстрировать наследование. Теперь класс External является наследником класса Base. И поэтому от туда был убран метод getThis() и добавлены конструкторы. Класс Main тоже претерпел небольшие изменения, туда была добавлена одна строка.
Обратите внимание что анонимный класс находящийся внутри класса External так же является наследником класса Base.
С теорией по внутренним классам вроде пока все 🙂 В следующем посте немного попрактикуемся по всей теме внутренних классов. Может по ходу практики еще всплывет что-то, что возможно было упущено в теории.



















