Порядок инициализации производного класса описан в справочнике по языку: Порядок инициализации производного класса , и в этом разделе также объясняется, почему это плохая (и потенциально опасная) практика использовать открытый член в логика инициализации вашего класса.
По сути, в тот момент, когда выполняется конструктор суперкласса (включая инициализаторы его свойств и блоки init
), конструктор производного класса еще не запускался. Но переопределенные члены сохраняют свою логику даже при вызове из конструктора суперкласса. Это может привести к переопределенному члену, который зависит от некоторого состояния, специфичного для производного класса, вызываемого из супер-конструктора, что может привести к ошибке или сбою во время выполнения. Это также один из случаев, когда вы можете получить NullPointerException
в Котлине.
Рассмотрим этот пример кода:
open class Base {
open val size: Int = 0
init { println("size = $size") }
}
class Derived : Base() {
val items = mutableListOf(1, 2, 3)
override val size: Int get() = items.size
}
(работающий образец)
Здесь переопределенный size
зависит от правильной инициализации items
, но в тот момент, когда size
используется в супер-конструкторе, поле поддержки items
по-прежнему содержит ноль. Поэтому создание экземпляра Derived
создает NPE.
Безопасное использование рассматриваемой практики требует значительных усилий, даже если вы не делитесь кодом с кем-либо еще, а когда вы это делаете, другие программисты обычно ожидают, что открытые члены будут безопасны для переопределения, затрагивая состояние производного класса.
Как правильно заметил @ Боб Даглиш , вы можете использовать отложенную инициализацию для свойства code
:
var code by lazy { calculate() }
Но тогда вам нужно быть осторожным и не использовать code
где-либо еще в логике построения базового класса.
Другим вариантом является требование передачи code
конструктору базового класса:
abstract class Base(var code: Int) {
abstract fun calculate(): Int
}
class Derived(private val x: Int) : Base(calculateFromX(x)) {
override fun calculate(): Int =
calculateFromX(x)
companion object {
fun calculateFromX(x: Int) = x
}
}
Это, однако, усложняет код производных классов в тех случаях, когда одна и та же логика используется как в переопределенных членах, так и для вычисления значений, передаваемых в супер-конструктор.