Ініціалізація змінної Котліна для дочірнього класу поводиться дивно для ініціалізації змінної зі значенням 0


16

Я створив таку ієрархію класів:

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викликає іншу поведінку, ніж та, яка має інше значення?


4
Не пов'язане безпосередньо, але виклик методів перезапису з боку конструкторів, як правило, не є хорошою практикою, оскільки це може призвести до несподіваної поведінки (і ефективно розірвати контракт суперкласса / інваріантів з підкласів).
Адам Хошек

Відповіді:


18

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

Виклик конструктора B викликає конструктор A, який викликає функцію f друку "x in f: 1", після ініціалізації A решта B ініціалізується.

Отже, по суті, значення значення перезаписується.

(Коли ви ініціалізуєте примітиви з їх нульовим значенням у Котліні, вони технічно просто не ініціалізуються)

Ви можете спостерігати за цим "перезаписуванням" поведінки, змінюючи підпис з

var x: Int = 0 до var x: Int? = 0

Оскільки xце більше не є примітивним int, поле насправді отримує ініціалізацію до значення, створюючи вихід:

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

5
Коли ви ініціалізуєте примітиви з їх нульовим значенням у Котліні, вони технічно просто не ініціалізуються - це те, що я хотів прочитати ... Дякую!
deHaar

Це все ще здається помилкою / непослідовністю.
Кроппеб

2
@Kroppeb це просто Java, таку ж поведінку можна спостерігати лише в коді Java. Це не має нічого спільного з Котлін
Sxtanna

8

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

Якщо будь-яке з цих властивостей використовується в логіці ініціалізації базового класу (прямо чи опосередковано через іншу переосмислену реалізацію відкритого члена), це може призвести до неправильної поведінки або збою в процесі виконання. При розробці базового класу слід уникати використання відкритих членів у конструкторах, ініціалізаторах властивостей та init-блоках.

UPD:

Існує помилка, яка викликає цю непослідовність - https://youtrack.jetbrains.com/issue/KT-15642

Якщо властивість призначається як побічний ефект виклику віртуальної функції всередині супер конструктора, його ініціалізатор не перезаписує властивість, якщо вираз ініціалізатора є типовим значенням за замовчуванням (null, примітивний нуль).


1
Крім того, IntelliJ попереджає про це. Виклик f()у initблок Aподає попередження "Виклик
нефінальної

У наданій вами документації йдеться про те, що "ініціалізація базового класу робиться як перший крок і, таким чином, відбувається до запуску логіки ініціалізації похідного класу", що саме відбувається в першому прикладі запитання. Однак у другому прикладі інструкція про ініціалізацію var x: Int = 0похідного класу взагалі не виконується, що суперечить тому, що йдеться в документації, що приводить мене до думки, що це може бути помилка.
Subaru Таширо

@SubaruTashiro Так, ти маєш рацію. Це ще одне питання - youtrack.jetbrains.com/issue/KT-15642 .
ваньочек
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.