Kotlin инициализация переменной для дочернего класса ведет себя странно при инициализации переменной со значением 0 - PullRequest
16 голосов
/ 20 января 2020

Я создал следующую иерархию классов:

open class A {
    init {
        f()
    }

    open fun f() {
        println("In A f")
    }
}

class B : A() {
    var x: Int = 33

    init {
        println("x: " + x)
    }

    override fun f() {
        x = 1
        println("x in f: "+ x)
    }

    init {
        println("x2: " + x)
    }
}

fun main() {
    println("Hello World!!")
    val b = B()
    println("in main x : " + b.x)
}

Вывод этого кода:

Hello World!!
x in f: 1
x: 33
x2: 33
in main x : 33

Но , если я изменяю инициализацию x с

var x: Int = 33

до

var x: Int = 0

вывод показывает вызов метода в отличие от вывода выше:

Hello World!!
x in f: 1
x: 1
x2: 1
in main x : 1

Кто-нибудь знает, почему инициализация с 0 вызывает другое поведение, чем поведение с другим значением?

Ответы [ 2 ]

18 голосов
/ 20 января 2020

суперкласс инициализируется перед подклассом.

Вызов конструктора B вызывает конструктор A, который вызывает функцию f, печатающую "x in f: 1", после инициализации A остальные Инициализирован B.

Таким образом, по существу, настройка значения перезаписывается.

(Когда вы инициализируете примитивы с их нулевым значением в Kotlin, они технически просто не инициализируются вообще)

Вы можете наблюдать это поведение "перезаписи", изменив сигнатуру с

var x: Int = 0 на var x: Int? = 0

Поскольку x больше не является примитивом int поле фактически инициализируется значением, что приводит к выводу:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
8 голосов
/ 20 января 2020

Это поведение описано в документации - https://kotlinlang.org/docs/reference/classes.html#derived -class-initialization-order

Если какое-либо из этих свойств используется в логиках инициализации базового класса c (прямо или косвенно, через другую переопределенную реализацию открытого члена), это может привести к некорректному поведению или сбою во время выполнения. Поэтому при проектировании базового класса следует избегать использования открытых членов в конструкторах, инициализаторах свойств и блоках инициализации.

UPD:

Существует ошибка, приводящая к этой несовместимости: https://youtrack.jetbrains.com/issue/KT-15642

Когда свойство назначается как побочный эффект вызова виртуальной функции внутри супер-конструктора, его инициализатор не перезаписывает свойство, если выражение инициализатора имеет вид значение по умолчанию для типа (ноль, примитивный ноль).

...