На мой взгляд, Java имеет богатые и интересные возможности и по инициализации объектов. Взять например инициализационные статические и нестатические блоки — очень удобный инструмент. Сегодня мы упомянем различные способы инициализации, но настоящая наша цель — это разобраться с порядоком инициализации унаследованных объектов. Что произойдет раньше — выполнится код в инициализационном блоке родителя или инициализированно нестатическое поле наследника? Об этом и многом другом наш сегодняшний разговор.

Порядок инициализации простого объекта наследника Object

Начнем с простого: в каком порядке инициализируется объект (в нашем первом случае — наследний Object, простенький такой)?

Проведем эксперемент при помощи следующего кода:

    public static void main(String[] args) {
        new A();
    }

public static int fooInit(String who) { System.out.println(String.format("%s initialized", who)); return 0; }

public static void print(String what) { System.out.println(what); }

public static class A { private int instanceFieldA = fooInit("Class A instance field"); private static int staticFieldA = fooInit("Class A static(class) field"); //начало экземплярного инициализационного блока { print("Initialization block of A is called"); } //начало статического инциализационного блока static { print("Static initialization block of A is called"); }

public A() { print("Class A constructor called"); }

}

У нас в наличии: статический инициализационный блок, нестатический (дальше я буду называть его «экземплярный») инициализационный блок, статические поле и экземплярное поле. Дополняет все это дело конструктор без параметров.

Теперь, перед тем как я напишу что же мы получим в консоли, я предлагаю вам выдвинуть свои версии.

Выдвинули? А теперь версия JVM :)


Class A static(class) field initialized
Static initialization block of A is called
Class A instance field initialized
Initialization block of A is called
Class A constructor called

Делаем выводы (пока первая итерация):

  1. Первыми инициализируются (вызываются) статические элементы класса
  2. После статических иниализируются (вызываются) экземплярные поля и блоки
  3. Самым последним отрабатывает конструктор

Окей, теперь давайте поменяем порядок объявления членов класса, а точнее — поменяем поставим статический блок раньше статического поля. Вот так:

        static {
            print("Static initialization block of A is called");
        }
        private static int staticFieldA = fooInit("Class A static(class) field");

На что мы получим:


Static initialization block of A is called
Class A static(class) field initialized
Class A instance field initialized
Initialization block of A is called
Class A constructor called

И из этого сделаем выводы (а точнее — дополним ранее сделанные выводы), что:

  1. Статические поля (блоки инициализации) инициализируются (выполняются) в порядке их объявления
  2. Экземплярные поля (блоки инициализации) инициализируются (выполняются) в порядке их объявления

Теперь исследуем поведение статических членов класса, для этого изменим код метода main() следующим образом:

    public static void main(String[] args) {
        new A();
        print("-----");
        new A();
    }

На что консоль ответит так:


Class A static(class) field initialized
Static initialization block of A is called
Class A instance field initialized
Initialization block of A is called
Class A constructor called
-----
Class A instance field initialized
Initialization block of A is called
Class A constructor called

Как вы можете видеть, второй раз статической инициализации не было.

А это потому, что выполнение статического кода происходит только один раз и во время первой загрузки класса.

Инициализация иерархии объектов

Выше мы рассматривали ситуацию, когда наследование происходит от Object, это простой случай. Теперь перейдем к чуть более сложному и создадим иерархию объектов.

Добавим к классу A дочерник класс B (вот такие оригинальные имена классов :) ):

    public static class B extends A {
        private int instanceFieldB = fooInit("Class B instance field");
        private static int staticFieldB = fooInit("Class B static(class) field");

{ print("Initialization block of B is called"); }

static { print("Static initialization block of B is called"); }

public B() { print("Class B constructor called"); }

}

Суть таже: во время инициализации класса в консоли появляются сообщения. Метод main() приведем к виду:

    public static void main(String[] args) {
        new B();
    }

Запускаем это дело и видим:


Class A static(class) field initialized
Static initialization block of A is called
Class B static(class) field initialized
Static initialization block of B is called
Class A instance field initialized
Initialization block of A is called
Class A constructor called
Class B instance field initialized
Initialization block of B is called
Class B constructor called

Сколько всего! А ведь мы только один класс добавили в иерархию. Давайте делать выводы:

  1. Инициализационные блоки наследуются — что явно видно из примера. Поэтому с ними нужно быть осторожными, так как переопределить их в потомках не получится.
  2. Статические члены классов инициализируются в порядке «от родителя к наследнику» самыми первыми (при первой загрузке/обращении к классу).
  3. После инициализации статических членов начинается экземплярная инициализации. Порядок ее таков: все нестатические члены родителя, конструктор родителя, все нестатические члены наследника, конструктор наследника.

Надеюсь, вы разобрались с инициализацией. Самый лучший способ все это запомнить — это повторить мой экперимент. Его вариаций огромное множесто, так что дерзайте!)

Вопрос вместо заключения

Так как вы уже человек просвященный в вопросах инициализации, то я хочу напоследок предложить эксперимент (правда он затрагивает наследование, но не думаю что вас это остановит).

Внимание вопрос: каков будет результат выполнения нижеприведенного кода? А самый главный вопрос «почему он будет таким?» :)

public class TestYourSelf {

public static void main(String[] args) { new Child(); }

static class Parent { Parent() { foo(); }

void foo() { //empty method } }

static class Child extends Parent { private String x = "hello-buddy";

@Override void foo() { System.out.println(x.length()); } }

}

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

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