Зачем использовать ключевое слово final при создании анонимных классов
— Я расскажу тебе сегодня про несколько ключевых слов в Java. Но начну с самого интересного – ключевого слова final. Если перевести его с английского, то получится что-то вроде финальный или окончательный.
Ключевое слово final можно добавлять при объявлении переменной, метода и класса.
— А зачем нужен этот final?
— Все довольно просто. Если мы пометили переменную словом final, то она становится неизменной:
final int i = 5; i++; //ошибка компиляции – нельзя менять значение переменной i |
— Если мы пометили метод словом final, то этот метод запрещено переопределять в классах-наследниках:
— Ясно. А зачем может понадобиться запрещать переопределение метода?
— Например, программист написал в этом методе много важного кода и хочет, чтобы все наследники его класса гарантированно имели заданное поведение.
И наконец – третье.
Если мы пометим словом final класс, то таким образом мы запретим наследоваться от него.
— А зачем запрещать наследование классов?
— Ты должен понять, что запрет наследования идет не из вредности, а ради безопасности и целостности кода. Если наследование класса не запрещено, то подразумевается, что оно разрешено. И код проектировщика класса будет нормально работать и с объектами его класса и с объектами класса-наследника.
А вот если разработчик видит, что даже при небольших изменениях в его классе, все перестанет работать, как задумано, тогда ему лучше запретить наследование.
— Класс String, например, объявлен как final, как и все примитивные типы: Integer, Boolean, Double, Character,…
— Ага, понимаю. Класс String сделан immutable и если бы вдруг появились изменяемые строки, то много чего перестало бы работать.
— Ну, почти. Скажем так, все работало бы почти по-старому, но иногда возникали бы ошибки, которые было бы очень сложно найти и понять. Поэтому, в некоторых случаях наследование классов или методов не грех и запретить – меньше потом ошибок вылавливать.
— А где еще можно писать final?
— final можно писать перед переменными-аргументами функции и перед переменными в методе. Вот пример:
public void setName(final String name) final String displayName = «Mr.»+name; … this.name = displayName; > |
— А какой в этом смысл?
— Ну, смыслов – два. Во-первых, мы объявляем переменную final – если хотим сообщить другим разработчикам, что это значение – определенная константа, а не просто переменная.
Например, мы хотим рассчитать НДС от цены:
public int getPriceNDS() final int NDS = 20; return this.price * NDS / 200; > |
И во-вторых, если мы будем писать локальные или анонимные внутренние классы, то такие переменные нам понадобятся. Я расскажу о таких классах в ближайшее время. Но не сегодня.
— Ок, пока вроде ничего сложного.
— Обрати внимание, что неизменяемой становится только переменная, а не объект, на который она ссылается. Объект можно менять еще как.
— Как раз хотел спросить этот момент. А нет способа сделать объект неизменным?
— Нет, только если ты напишешь immutable класс.
Обрати внимание на такой момент — т.к. значение переменной менять нельзя, то ей сразу нужно присвоить начальное значение:
Но, вместе с тем, Java разрешает перенести инициализацию final-переменных класса в конструктор.
Более того, в разных конструкторах final-переменные можно инициализировать разными значениями. Это очень удобно:
public Home()
height = 100;
width = 200;
>
public Home(int width)
this.height = 300;
this.width = width;
>
— Действительно интересная тема, и абсолютно все понятно, спасибо, Билаабо!
Что означает модификатор final в полях классов?
Я заметил, что профессиональные разработчики нередко объявляют поля классов в джаве как final, например:
@Component public class LinkResolver implements GraphQLResolver < private final UserRepository userRepository; public LinkResolver(UserRepository userRepository) < this.userRepository = userRepository; >public User postedBy(Link link) < if (link.getUserId() == null) < return null; >return userRepository.findById(link.getUserId()); > >
Для чего это делается? Я видел, как в проекте final объявляются не только поля сервисов и репозиториев, но и поля дата-классов. Расскажите об этом больше, пожалуйста.
Отслеживать
18.5k 4 4 золотых знака 31 31 серебряный знак 45 45 бронзовых знаков
задан 5 мар 2019 в 9:57
2,427 2 2 золотых знака 30 30 серебряных знаков 47 47 бронзовых знаков
Чтобы можно было только один раз инициализировать переменную, например только в конструкторе
5 мар 2019 в 10:00
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
- Удобно чисто визуально. Если видишь, что переменная final , то точно знаешь, что она не будет меняться.
- Без final никак, если ты локальную переменную собираешься использовать в анонимных классах/замыканиях.
- final — это подсказка компилятору. Если он видит финальные поля, то может произвести определённые оптимизации кода.
Отслеживать
ответ дан 5 мар 2019 в 10:03
Suvitruf — Andrei Apanasik Suvitruf — Andrei Apanasik
32.2k 15 15 золотых знаков 60 60 серебряных знаков 93 93 бронзовых знака
А final, примененное к полям, может иметь какое-то значение при наследовании?
5 мар 2019 в 10:29
@typemoon принципиальной нет. Поля всё также остаются неизменяемыми.
5 мар 2019 в 10:31
в ответе кроется одно коварство, некоторые новички путают final с иммутабельностью.
5 мар 2019 в 10:45
4. final-поля безопасно публикуются в многопоточном окружении
5 мар 2019 в 10:46
@StrangerintheQ ещё про рефлексию можно написать )
5 мар 2019 в 11:20
Многа букав: По-умолчанию ставятся максимальные ограничения. Снимать ограничение (final) с поля имеет смысл только если на это есть причина (поле изменяется).
С помощью final отмечаются поля, которые инициализируются только один раз. Отмечать такое поле как final технически необязательно, но если этого не сделать, то:
- останется возможность ошибки: разработчик опечатается и переприсвоит значение;
- при чтении кода возникнут вопросы: «Где это поле изменяется? И как это повлияет на остальной код?». Модификатор final говорит разработчику что лишние сценарии рассматривать не требуется.
Вообще значительная часть программирования это расстановка ограничений. Чем меньше операций можно выполнить с кодом — тем легче о нем рассуждать — тем проще проверить его правильность. Поэтому разработчики стараются устанавливать для полей/переменных максимально жесткие ограничения, чтобы снизить количество возможных сценариев их использования.
По аналогичной причине поля, которые не используются вне класса, объявляются как private . Поле с доступом по-умолчанию оставит лишние сценарии использования и, вместе с ними, простор для ошибок и вопросов («Из какого класса к нему идет обращение и зачем?»).
Многие IDE и средства анализа кода (например, PMD) отслеживают поля и переменные, которые могут быть отмечены как final и выдают соответствующие предупреждения.
На практике поля, как важную часть классов, везде где возможно отмечают как final. На локальных переменных и аргументах методов же обычно «экономят», т.к. срок жизни у них короче и риск побочных эффектов меньше. Для них наоборот используют final только там где это явно необходимо (использование в анонимных классах). Например, в Вашем коде аргументы конструктора ( userRepository ) и метода ( link ) могли бы быть отмечены как final , но не отмечены для простоты кода.
Анонимные классы
Если вы много программировали на языке 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 автоматически присвоит ему имя по правилу
где 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 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта