Инициализаторы
Наконец, последней допустимой конструкцией в теле класса является объявление инициализаторов. Записываются объектные инициализаторы очень просто – внутри фигурных скобок.
public class Test { private int x, y, z;
// инициализатор объекта { x=3; if (x>0) y=4; z=Math.max(x, y); } }
Инициализаторы не имеют имен, исполняются при создании объектов, не могут быть вызваны явно, не передаются по наследству (хотя, конечно, инициализаторы в родительском классе продолжают исполняться при создании объекта класса-наследника).
Было указано уже три вида инициализирующего кода в классах – конструкторы, инициализаторы переменных, а теперь добавились объектные инициализаторы. Необходимо разобраться, в какой последовательности что выполняется, в том числе при наследовании. При создании экземпляра класса вызванный конструктор выполняется следующим образом:
- если первой строкой идет обращение к конструктору родительского класса (явное или добавленное компилятором по умолчанию), то этот конструктор исполняется;
- в случае успешного исполнения вызываются все инициализаторы полей и объекта в том порядке, в каком они объявлены в теле класса;
- если первой строкой идет обращение к другому конструктору этого же класса, то он вызывается. Повторное выполнение инициализаторов не производится.
Второй пункт имеет ряд важных следствий. Во-первых, из него следует, что в инициализаторах нельзя использовать переменные класса, если их объявление записано позже.
Во-вторых, теперь можно сформулировать наиболее гибкий подход к инициализации final-полей. Главное требование – чтобы такие поля были проинициализированы ровно один раз. Это можно обеспечить в следующих случаях:
- если инициализировать поле при объявлении;
- если инициализировать поле только один раз в инициализаторе объекта (он должен быть записан после объявления поля);
- если инициализировать поле только один раз в каждом конструкторе, в первой строке которого стоит явное или неявное обращение к конструктору родителя. Конструктор, в первой строке которого стоит this, не может и не должен инициализировать final-поле, так как цепочка this-вызовов приведет к конструктору с super, в котором эта инициализация обязательно присутствует.
Для иллюстрации порядка исполнения инициализирующих конструкций рассмотрим следующий пример:
public class Test { { System.out.println("initializer"); } int x, y=getY(); final int z; { System.out.println("initializer2"); } private int getY() { System.out.println("getY() "+z); return z; } public Test() { System.out.println("Test()"); z=3; } public Test(int x) { this(); System.out.println("Test(int)"); // z=4; - нельзя! final-поле уже // было инициализировано } }
После выполнения выражения new Test() на консоли появится:
initializer getY() 0 initializer2 Test()
Обратите внимание, что для инициализации поля y вызывается метод getY(), который возвращает значение final-поля z, которое еще не было инициализировано. Поэтому в итоге поле y получит значение по умолчанию 0, а затем поле z получит постоянное значение 3, которое никогда уже не изменится.
После выполнения выражения new Test(3) на консоли появится:
initializer getY() 0 initializer2 Test() Test(int)