Порядок инициализации класса, при создании его через new
, выглядит следующим образом:
родительского
класса;дочернего
класса;родительского
класса;родительского
класса;дочернего
класса;дочернего
класса.Рассмотрим на примере двух классов. Один является наследником другого.
//Родительский класс
public class Animal {
public static String name = "Abstract Animal";
public Animal() {
System.out.println("My name is " + name);
}
}
//Класс наследник
public class Cat extends Animal {
public static String name = "Johnny Catswill";
public Cat() {
System.out.println("My name is " + name);
}
}
Попробуем создать класс Cat
и посмотрим на результат.
public class Main {
public static void main(String[] args) {
new Cat();
}
}
Вывод в консоль:
My name is Abstract Animal
My name is Johnny Catswill
Можно включить режим отладки в Intellij IDEA и пройтись по шагам исполнения программы.
Начало программы происходит с запуска main
метода, в котором вызывается конструторк класса Cat
. Здесь не происходит ничего интересного, можно выполнить следующий шаг программы, нажав F7
.
Курсор выполнения программы переместился на строку номер 8, в которой выполняется присвоение значения статической переменной name
класса родителя Animal
. Хотя в методе main
был вызван класс Cat
, сначала инициализируются статика родительского класса.
В нижней части картинки видны данные которые доступны на момент точки останова у текущего потока. Здесь видно, что переменные name
и weight
созданы и проинициализированы начальными значениями. Ссылочные типы ининциализируются как null
, а числовые 0
. Поэтому пока код в строке 8 не исполнился, поле name = null
. Упоминания о нестатических полях класса (переменной sex
) пока нет совсем.
Перейдем к следующему шагу программы, для этого нажимаем F7
.
Теперь, как видно, курсор исполнения переключился на строку номер 9 и дебаггер подсказывает нам, что переменная name
теперь хранит значение Abstract Animal
.
Следущее нажатие F7
приведет нас в дочерний класс Cat
для инициализации его статических полей.
Курсор выполнения остановился на первом статическом поле, которое, как и у родительского класса, изначально было равно null
.
Также мы можем увидеть, что поле weght
родительского класса Animal
уже проинициализировано и равно 3
.
Перемещая курсор выполнения дальше, при помощи F7
, мы окажемся в строке 10, в которой уже инициализируются нестатические поля родительского класса. Это соответствует пункту №3 в начале документа.
Здесь можно заметить, что в дебагере появился некий объект this
, который хранит две переменные с названием sex
. Для удобства каждая из них помечена к какому классу относится.
Сам this
указывает на объект Main$Cat@700
, то есть на класс Cat
. Внутри него есть переменная sex
- это переменная класса Cat
. А вот перменная класса Animal
хранится в виде Main$Animal.sex
. Обе переменные при создании инициализируются базовым значением по умолчанию. Для переменных типа boolean
это значение = false
.
Для того чтобы обратиться к этим переменным, например из класса Cat
, нужно использоваться следующую нотацию:
this
. Такое обращение приведет нас к переменной класса Cat
this.sex = true;
//или
sex = true;
super
. Это приведет нас к переменной родительского класса Animal
super.sex = true;
Продолжим исполнение программы кнопкой F7
и продвинемся до строки номер 13. Таким образом, чтобы строка 12 была исполнена.
Курсор исполнения окажется в конце конструктора родительского класса. На картинке видно, что на предыдущем шаге переменной sex
, родительского класса, присвоилось нужное нам значение, а в консоли вывелось My name is Abstract Animal
- это является результатом исполнения строки 12.
Продвигаясь вперед мы окажемся в строке номер 18, где происходит инициализация нестатических переменных дочернего класса. А после мы окажемся в конструкторе дочернего класса.
Как видно из картинки выше, переменная дочернего класса sex
теперь равна true
и на экране в консоле появилась надпись из конструтора класса Cat
.
На этом процесс инициализации завершается. Нажав пару раз кнопку F7
программа завершится, так как кроме создания объекта Cat
в ней больше ничего не написано.
Помимо создания примитивных переменных в статических полях можно создать объект другого класса. Что будет, если во время инициализациия статического поля родительского класса, создать объект дочернего?
private static class Animal {
public static String name = "Abstract Animal";
public static Cat cat = new Cat(); //создание объекта дочернего класса
public boolean sex = true;
public Animal() {
System.out.println("My name is " + name);
}
}
На шаге в строке 4, инициализация прервется и пойдет сначала по тому же алгоритму, но будут пропущены некоторые шаги. Статические переменные имеют область видимости в рамках всего класса, поэтому заново их инициализировать не нужно, а вот локальные переменные экземпляра класса относятся к области видимости созданного объекта. Поэтому переменная родительского класса Animal.sex
будет проинициализирована два раза, один раз для области видимости объекта созданного в методе main
, а второй раз для области видимости объекта созданного в родительском классе Animal
на строке 4.
Интересный факт, так как мы будем создавать объект в момент когда происходит иницилазиця класса Cat
, то инициализация всех статических полей будет пропущена, поэтому в консоли мы увидим следующую картину:
My name is Abstract Animal
My name is null
My name is Abstract Animal
My name is Johny
My name is null
это строка, которая появится в результате вызова конструктора, для объекта класса Cat
созданного в статическом поле родительского класса. И так как инициализация статики для него пропущена, то в переменной name
будет null
.