Init-блоки и вызов super в Kotlin отвечают за корректную инициализацию объектов с учетом иерархии классов. Это фундаментальные механизмы, влияющие на работу конструкций и наследование.
В Java конструкторы можно вызывать явно с помощью super(...) или неявно по умолчанию. В Kotlin всё иначе: существует четкое разделение между primary и secondary constructors, а init-блок используется для инициализации.
Основная сложность — правильно понять, когда инициализируются свойства, когда вызываются init-блоки и как строится порядок вызова конструкторов особенно при наследовании классов и комбинировании primary/secondary конструкторов.
Пример кода:
open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // Вывод: // Base init: Ivan // Derived init: Ivan, 25
Ключевые особенности:
В каком порядке выполняются init-блоки в цепочке наследования?
Сначала выполняются init-блоки суперкласса после инициализации его свойств, затем init-блоки подкласса после собственных свойств.
Пример кода:
open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B
Можно ли вызвать super() не из конструктора в Kotlin?
Нет, вызов super класса осуществляется только как часть объявления конструктора предоставляя параметры суперконструктору.
Инициализация secondary constructor может не вызывать primary в Kotlin?
Нет, все secondary конструкторы обязаны делегировать вызов либо другому secondary, либо primary конструктору. Primary, в свою очередь, вызывает суперконструктор.
Разработчик пытается обратиться к свойству, инициализируемому в подклассе, в init-блоке супер-класса:
open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Плюсы:** - Использование полиморфизма. **Минусы:** - Свойство s еще не проинициализировано, во время вызова init супер-класса — результат null или ошибка. ## Позитивный кейс Разделение инициализации так, чтобы не вызывать переопределяемые методы или свойства из конструктора: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **Плюсы:** - Нет обращения к неинициализированным данным; - Гарантирован порядок инициализации. **Минусы:** - Увеличивается количество кода для правильной инициализации.