#java
Читая книгу Эккеля, натолкнулся на один момент, который не могу понять. Представлен следующий код: import static net.mindview.util.Print.*; class Insect { private int i = 9; protected int j; Insect() { print("i = " + i + ", j = " + j); j = 39; } private static int x1 = printInit("static Insect.x1 initialized"); static int printInit(String s) { print(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k initialized"); public Beetle() { print("k = " + k); print("j = " + j); } private static int x2 = printInit("static Beetle.x2 initialized"); public static void main(String[] args) { print("Beetle constructor"); Beetle b = new Beetle(); } } OUTPUT: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor i = 9, j = 0 Beetle.k initialized k = 47 j = 39 Всегда думал, что перед вызовом конструктора, должны проиницилизоваться поля класса, чтобы в случае обращения к неициализирвоанной переменной в конструкторе не получить исключение. Следуя этой логике вывод на экран надписи "Beetle.k initialized" должен был быть после вывода "Beetle constructor". Прощу помочь разобраться. Спасибо.
Ответы
Ответ 1
Порядок инициализации таков: Статические элементы родителя Статические элементы наследника Глобальные переменные родителя Конструктор родителя Глобальные переменные наследника Конструктор наследника Пример class Insect { private int i = 9; [9] protected int j; [10] Insect() { [8] print("i = " + i + ", j = " + j); [11] j = 39; [12] } private static int x1 = printInit("static Insect.x1 initialized"); [2] static int printInit(String s) { [3] print(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k initialized"); [13] public Beetle() { [7] print("k = " + k); [14] print("j = " + j); [15] } private static int x2 = printInit("static Beetle.x2 initialized"); [4] public static void main(String[] args) { [1] print("Beetle constructor"); [5] Beetle b = new Beetle(); [6] } }Ответ 2
Порядок инициализации экземпляра объекта описан в JLS (12.5 Creation of New Class Instances) (перевод мой): При создании нового экземпляра класса, выделяется память для всех переменных экземпляра, объявленных в классе, и для всех переменных экземпляра, объявленных в каждом суперклассе, включая все скрытые переменные (§8.3). Если выделить память невозможно из-за нехватки свободного места, создание экземпляра прерывается с OutOfMemoryError. Иначе все переменные экземпляра нового объекта, включая объявленные в суперклассе, инициализируются значениями по-умолчанию. (§4.12.5). Перед тем, как будет возвращена ссылка на созданный объект, указанный конструктор выполняется для инициализации нового объекта по следующему алгоритму: Присвоить аргументы конструктора переменным-параметрам для вызова этого конструктора. Если конструктор начинается с явного вызова (§8.8.7.1) другого конструктор этого же класса (с использованием this), то нужно вычислить аргументы и выполнить тот конструктор рекурсивно, используя эти же 5 шагов. Если выполнение того конструктора будет прервано (completes abruptly), то этот алгоритм будет прерван по тем же причинам, иначе перейти к шагу 5. Если конструктор не начинается с явного вызова другого конструктора этого же класса (с использованием this), то для классов, отличных от Object явно или неявно вызывается конструктор суперкласса (используя super). Нужно вычислить аргументы и выполнить конструктор суперкласса рекурсивно, используя эти же 5 шагов. Если выполнение того конструктора будет прервано (completes abruptly), то этот алгоритм будет прерван по тем же причинам, иначе перейти к шагу 4. Выполнить инициализаторы экземпляра (instance initializers) и инициализаторы переменных экземпляра (instance variable initializers) для этого класса, с присвоением значений инициализаторов переменных экземпляра соответствующим переменным экземпляра, слева на право в порядке появления в исходном коде класса. Если выполнение любого инициализатора вызывает исключение, следующие инициализаторы не обрабатываются, а этот алгоритм завершается с тем же исключением. Иначе перейти к шагу 5. Выполнить остальное тело конструктора. Если выполнение будет прервано, то этот алгоритм будет прерван по тем же причинам. Иначе алгоритм завершится нормально. В отличие от C++, Java не устанавливает отдельные правила для диспетчеризации методов во время создания нового экземпляра класса. Если вызываются методы, переопределенные в наследниках в инициализируемом объекте, эти переопределенные методы используются до полной инициализации нового объекта. Порядок инициализации класса описан в JLS (12.4. Initialization of Classes and Interfaces): Инициализация класса состоит из выполнения его статических инициализаторов и инициализаторов для статических полей (переменных класса), объявленных в классе. Инициализация интерфейса состоит из выполнения инициализаторов полей (констант), объявленных в интерфейсе. Класс или интерфейс T будет проинициализирован непосредственно перед одним из следующих событий: T - класс и создается экземпляр класса T. Вызов статического метода, объявленного в T. Выполняется присваивание статическому полю, объявленному в T. Статическое поле, объявленное в T используется, и это поле не является константой (§4.12.4) (константа - final переменная примитивного типа или String, объявленная с инициализатором, являющимся константным выражением) T - класс верхнего уровня (§7.6) и выполняется выражение assert (assert statement) (§14.10) лексически расположенное внутри T (§8.1.3). Когда класс инициализируется, его суперклассы инициализируются (если они не были инициализированы ранее); Суперинтерфейсы так же инициализируются (§8.1.5), если в них объявлены методы по-умолчанию (default methods) (§9.4.3) (если они не были инициализированы ранее). Инициализация интерфейса сама по себе не вызывает инициализацию его суперинтерфейсов. Обращение к статическому полю (static field) (§8.3.1.1) вызывает инициализацию только для класса или интерфейса, объявившего это поле, даже если обращение было выполнено с использованием имени субкласса, субинтерфейса, или класса, реализующего интерфейс. Вызов некоторых методов из класса Class и пакета java.lang.reflect так же вызывают инициализацию класса или интерфейса. Класс или интерфейс не будет инициализирован при других обстоятельствах. Цель состоит в том, чтобы инициализаторы класса или интерфейса переводили его в непротиворечивое состояние, и это состояние было первым видимым из других классов состоянием. Статические инициализаторы и инициализаторы переменных класса выполняются в порядке появления в тексте, и не могут ссылаться на переменные класса, объявленные в тексте после использования, несмотря на то, что эти переменные класса входят в область видимости (§8.3.3). Это ограничение создана для обнаружения циклической или другой неправильной инициализации на этапе компиляции. Вооружившись этим корявым переводом, посмотрим, что происходит в вашем примере: import static net.mindview.util.Print.*; class Insect { private int i = 9; protected int j; Insect() { print("i = " + i + ", j = " + j); j = 39; } private static int x1 = printInit("static Insect.x1 initialized"); static int printInit(String s) { print(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k initialized"); public Beetle() { print("k = " + k); print("j = " + j); } private static int x2 = printInit("static Beetle.x2 initialized"); public static void main(String[] args) { print("Beetle constructor"); Beetle b = new Beetle(); } } OUTPUT: // Вызывается статический метод класса Beetle.main // Начинается инициализация класса Beetle // Инициализируется суперкласс Insect // Вызывается инициализатор для Insect.x1 static Insect.x1 initialized // Вызывается инициализатор для Beetle.x2 static Beetle.x2 initialized // Класс Beetle инициализирован, выполняется Beetle.main // выполняется print Beetle constructor // начинается создание экземпляра конструктором Beetle() // вызов this(..) в первой строке конструктора отсутствует (Beetle. шаг 2) // . будет вызван super() (Beetle. шаг 3) т.е. Insect() // внутри Insect() аналогично вызван Object() (Insect. шаг 3) // выполнение инициализации полей (Insect. шаг4) в порядке // . появления в тексте // i = 9; (поле i) // продолжаем выполнение конструктора Insect() (Insect. шаг 5) // Вывод полей в конструкторе: j инициализирован // . значением по-умолчанию на этапе выделения памяти i = 9, j = 0 // j = 39 // тело конструктора Insect() выполнено без ошибок // возвращаемя в Beetle(), переходим к инициализации полей (Beetle. шаг 4) // k = printInit("Beetle.k initialized") Beetle.k initialized // продолжаем выполнение конструктора Beetle() (Beetle. шаг 5) k = 47 j = 39 // тело конструктора Beetle() выполнено без ошибокОтвет 3
Так "Beetle constructor" выводится не в самом конструкторе, а перед вызовом конструктора ( в методе main). Т.е. у тебя сначала идет вывод "Beetle constructor", затем происходит вызов конструктора, в котором сначала инициализируется не статические переменные (потому как статические поля и блоки уже проинициализировались), а потом уже возвращается сам объект. Советую почитать инициализация полей
Комментариев нет:
Отправить комментарий