Сегодня мы поговорим о возможножности инициализировать поля во время создания объекта с помощью нестатических блоков инициализации.

[Данная статья подготовлена на основе материала из книги 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-разработчик».

Комментарии (1)


  1. Cdracm
    02.09.2023 15:55

    Ниже приведен вывод для кода из Листинга 2:

    извините, но на самом деле вывод из листинга 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