[Данная статья подготовлена на основе материала из книги OCP Oracle Certified Professional Java SE 17 Developer (Exam 1Z0-829) Programmer’s Guide, опубликованной издательством Oracle Press. — Ред.]
Инициализаторы нужны для установки начальных значений полей в объектах и классах. Существует три вида инициализаторов:
- Инициализирующие выражения
- Статические блоки инициализации
- Нестатические блоки инициализации
Инициализирующие выражения (initializer expressions) позволяют осуществлять инициализацию полей прямо в операторах объявления. Для этого значение в инициализирующем выражении должно быть совместимо по присваиванию с объявляемым полем.
Java также позволяет определять в классе статические блоки инициализации (static initializer blocks). Эти блоки могут содержать произвольный код, но в основном они используются для инициализации статических полей. Код в статическом блоке инициализации выполняется единожды, когда класс загружается в память.
Подобно тому, как статические блоки инициализации могут использоваться для инициализации статических полей именованного класса, Java предоставляет возможность инициализации полей во время создания объекта с помощью нестатических блоков инициализации (instance initializer blocks), о чем и пойдет речь в этой статье.
Во время создания объекта логика нестатических блоков инициализации выполняется перед логикой конструктора. Синтаксис нестатических блоков инициализации такой же, как и синтаксис обычных локальных блоков, что вы можете видеть в строке (2) кода примера, приведенного ниже. Код в локальном блоке выполняется каждый раз, когда создается инстанс этого класса.
class InstanceInitializers { long[] squares = new long[10]; // (1) // ... { // (2) Нестатический блок инициализации for (int i = 0; i < squares.length; i++) squares[i] = i*i; } // ... }
Сначала в строке (1) создается массив заданной длины под названием squares, а затем при создании каждого инстанса класса InstanceInitializers в строке (2) выполняется определенный нами нестатический блок инициализации. Обратите внимание, что этот блок инициализации не заключен в тело какого-либо метода этого класса. Класс может иметь несколько нестатических блоков инициализации, и они (а также любые инициализирующие выражения в объявлениях полей класса) будут выполняться в том порядке, в котором они определены.
Порядок объявления нестатических блоков инициализации
Аналогично другим инициализаторам, о которых мы упоминали ранее, нестатический блок инициализации не может использовать опережающую ссылку (forward reference) на поле просто по его имени в операциях чтения, поскольку это нарушает правило «объявление переменной всегда предшествует ее использованию» (declare-before-reading). Однако мы без проблем можем использовать для доступа к полю ключевое слово this.
Листинг 1 демонстрирует класс с нестатическим блоком инициализации в строке (1), который содержит опережающие ссылки на поля i, j и k, объявленные в строках (7), (8) и (9) соответственно. Доступ к этим полям в операциях чтения в строках (3), (4), (5) и (6) осуществляется с помощью this. Использование голого имени этих полей в строках (3), (4), (5) и (6) для доступа к их значениям нарушит упомянутое выше правило, что приведет к ошибке компиляции независимо от того, объявлены ли поля с применением инициализирующих выражений или являются final.
В строке (2) доступ к полям i и j осуществляется просто по имени, что разрешено в операциях записи. Однако нам нужно внимательно следить за правильностью инициализации полей. В строках (3), (4) и (5) поля i и j имеют значение 10. Однако после выполнения инициализирующих выражений в объявлениях полей значение j станет равным 100.
Листинг 1. Доступ к полям с помощью ключевого слова this
public class InstanceInitializersII { { // Нестатический блок инициализации с опережающими ссылками. (1) i = j = 10; // (2) Это разрешено. int result = this.i * this.j; // (3) i = 10, j = 10. System.out.println(this.i); // (4) 10 System.out.println(this.j); // (5) 10 System.out.println(this.k); // (6) 50 } // Объявления полей. int i; // (7) Объявление поля без инициализирующего выражения. int j = 100; // (8) Объявление поля с инициализирующим выражением. final int k = 50; // (9) Final поле с константным выражением. }
Листинг 2 иллюстрирует еще несколько тонкостей, касающихся нестатических блоков инициализации. В приведенном ниже коде в строке (4) происходит недопустимая опережающая ссылка, которая пытается прочитать значение поля nsf1 до его объявления. Операция чтения в строке (11) происходит после объявления, поэтому она не вызывает проблем. Опережающие ссылки, сделанные в левой части присваивания, разрешены, как показано в строках (2), (3), (5) и (7). В строках (5) и (12) вы можете увидеть объявление локальных переменных с использованием зарезервированного слова var.
Листинг 2. Нестатические блок инициализации и опережающие ссылки.
public class NonStaticForwardReferences { { // (1) Нестатический блок инициализации. nsf1 = 10; // (2) ОК. Присвоение nsf1 разрешено. nsf1 = sf1; // (3) OK. Доступ к статическому полю в нестатическом контексте. // int a = 2 * nsf1; // (4) Не ОК. Операция чтения до объявления. var b = nsf1 = 20; // (5) ОК. Присваивание nsf1 разрешено. int c = this.nsf1; // (6) ОК. Доступ с помощью this. } int nsf1 = nsf2 = 30; // (7) Нестатическое поле. Присвоение nsf2 разрешено. int nsf2; // (8) Нестатическое поле. static int sf1 = 5; // (9) Статическое поле. { // (10) Нестатический блок инициализации. int d = 2 * nsf1: // (11) OK. Операция чтения после объявления. var e = nsf1 = 50; // (12) OK. Присваивание nsf1 разрешено. } public static void main(String[] args) { NonStaticForwardReferences objRef = new NonStaticForwardReferences () ; System.out.println("nsf1: " + objRef.nsf1) ; System.out.println("nsf2: objRef.nsf2); } }
Ниже приведен вывод для кода из Листинга 2:
<code>nsf1: 50 nsf2: 30</code>
В нестатических блоках инициализации, как и в инициализирующих выражениях, для ссылки на текущий объект можно использовать ключевые слова this и super. (Оператор return же не допускается).
Нестатические блоки инициализации можно использовать для уменьшения объемов дублирования общего кода инициализации, который будет выполняться независимо от того, какой конструктор вызывается. В Листинге 3 показано, как анонимный класс, определенный в строке (1), использует для инициализации своих полей нестатический блок (строка (2)).
Листинг 3. Нестатический блок инициализации в анонимном классе
// File: InstanceInitBlock.java class Base { protected int a; protected int b; void print() { System.out.println("a: " + a); } } class AnonymousClassMaker { Base createAnonymous() { return new Base() { // (1) Анонимный класс { // (2) Нестатический блок инициализации a = 5; b = 10; } @Override void print() { super.print(); System.out.println("b: " + b); } }; // конец анонимного класса } } public class InstanceInitBlock { public static void main(String[] args) { new AnonymousClassMaker().createAnonymous().print(); } }
Ниже приведен вывод для кода из Листинга 3:
<code>a: 5 b: 10</code>
Обработка исключений в нестатических блоках инициализации
Обработка исключений в нестатических блоках инициализации аналогична обработке в статических блоках инициализации. Однако некоторые отличия все-таки имеются: выполнение нестатического блока инициализации может привести к возникновению не пойманного проверяемого исключения, если это исключение объявлено в блоке throws конструктора класса. Статические блоки инициализации не могут этого допустить, так как в такой инициализации класса не участвуют никакие конструкторы. Нестатические блоки инициализации в анонимных классах имеют большую степень свободы: они могут выбрасывать любые исключения.
В завершение приглашаем всех желающих на открытый урок, на котором поработаем с текстовыми файлами на примере реализации просто шифратора. На вебинаре создадим консольное приложение на Java с нуля, которое сможет шифровать и дешифровать текстовые файлы, используя очень простые алгоритмы шифрования. Записаться на открытый урок можно на странице курса «Java-разработчик».
ссылка на оригинал статьи https://habr.com/ru/articles/758478/
Добавить комментарий