На этом шаге мы рассмотрим порядок инициализации свойств.
Вы узнали, как инициализировать свойства и добавлять логику инициализации разными способами: объявлять параметры в главном конструкторе, инициализировать при объявлении, во вспомогательном конструкторе или в блоке инициализации. Одно и то же свойство может использоваться в нескольких инициализациях, поэтому порядок их выполнения очень важен.
Чтобы разобраться с этим, подробно исследуем порядок инициализации итогового поля и вызова методов в скомпилированном байт-коде Java. Рассмотрим следующий код, который объявляет класс Player и создает его экземпляр:
class Player(_name: String, val health: Int) { val race = "DWARF" var town = "Bavaria" val name = _name val alignment: String private var age = 0 init { println("initializing player") alignment = "GOOD" } constructor(_name: String) : this(_name, 100) { town = "The Shire" } } fun main(args: Array<String>) { Player("Madrigal") }
Обратите внимание, что экземпляр класса Player создается вызовом вспомогательного конструктора Player("Madrigal").
На рисунке 1 вверху показан класс Player, а внизу - декомпилированный байткод Java, отражающий итоговый порядок инициализации.
Рис.1. Порядок инициализации класса Player (скомпилированный байт-код)
Итак, инициализация выполняется в следующем порядке:
Порядок выполнения блока init (пункт 3) и операций присваивания на уровне класса (пункт 2) зависит от порядка, в котором они указаны. Если блок init поместить после операций присваивания значений свойствам на уровне класса, он выполнится после них.
Обратите внимание на отсутствие кода инициализации одного из свойств - age: хотя оно объявляется и инициализируется, оно находится на уровне свойств класса. Это связано с тем, что ему присваивается значение 0 (значение по умолчанию для простого типа int в Java), поэтому его можно не инициализировать явно, и компилятор просто оптимизировал код, опустив ненужные инструкции.
На следующем шаге мы рассмотрим задержку инициализации.