Все о нестатических блоках инициализации в 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-разработчик».


ссылка на оригинал статьи https://habr.com/ru/articles/758478/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *