Сегодня мы поговорим о возможножности инициализировать поля во время создания объекта с помощью нестатических блоков инициализации.
[Данная статья подготовлена на основе материала из книги 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) кода примера, приведенного ниже. Код в локальном блоке выполняется каждый раз, когда создается инстанс этого класса.
Сначала в строке (1) создается массив заданной длины под названием squares, а затем при создании каждого инстанса класса
Аналогично другим инициализаторам, о которых мы упоминали ранее, нестатический блок инициализации не может использовать опережающую ссылку (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. Доступ к полям с помощью ключевого слова
Листинг 2 иллюстрирует еще несколько тонкостей, касающихся нестатических блоков инициализации. В приведенном ниже коде в строке (4) происходит недопустимая опережающая ссылка, которая пытается прочитать значение поля
Листинг 2. Нестатические блок инициализации и опережающие ссылки.
Ниже приведен вывод для кода из Листинга 2:
В нестатических блоках инициализации, как и в инициализирующих выражениях, для ссылки на текущий объект можно использовать ключевые слова
Нестатические блоки инициализации можно использовать для уменьшения объемов дублирования общего кода инициализации, который будет выполняться независимо от того, какой конструктор вызывается. В Листинге 3 показано, как анонимный класс, определенный в строке (1), использует для инициализации своих полей нестатический блок (строка (2)).
Листинг 3. Нестатический блок инициализации в анонимном классе
Ниже приведен вывод для кода из Листинга 3:
Обработка исключений в нестатических блоках инициализации аналогична обработке в статических блоках инициализации. Однако некоторые отличия все-таки имеются: выполнение нестатического блока инициализации может привести к возникновению не пойманного проверяемого исключения, если это исключение объявлено в блоке
В завершение приглашаем всех желающих на открытый урок, на котором поработаем с текстовыми файлами на примере реализации просто шифратора. На вебинаре создадим консольное приложение на Java с нуля, которое сможет шифровать и дешифровать текстовые файлы, используя очень простые алгоритмы шифрования. Записаться на открытый урок можно на странице курса «Java-разработчик».
[Данная статья подготовлена на основе материала из книги 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-разработчик».
Cdracm
извините, но на самом деле вывод из листинга 2 будет таков:
/NonStaticForwardReferences.java:16: error: ';' expected
int d = 2 * nsf1: // (11) OK. Операция чтения после объявления.
^
/NonStaticForwardReferences.java:24: error: unclosed string literal
System.out.println("nsf2: objRef.nsf2);
^
2 errors