Inicjalizacja zmiennej Kotlin dla klasy potomnej zachowuje się dziwnie podczas inicjalizacji zmiennej o wartości 0

16

Utworzyłem następującą hierarchię klas:

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)
}

Dane wyjściowe tego kodu to

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

Ale jeśli zmienię inicjalizację xz

var x: Int = 33

do

var x: Int = 0

dane wyjściowe pokazują wywołanie metody w przeciwieństwie do danych wyjściowych powyżej:

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

Czy ktoś wie, dlaczego inicjalizacja za pomocą 0powoduje inne zachowanie niż ta o innej wartości?

PRATYUSH SINGH
źródło
4
Nie bezpośrednio powiązane, ale wywoływanie nadrzędnych metod z konstruktorów nie jest na ogół dobrą praktyką, ponieważ może prowadzić do nieoczekiwanego zachowania (i skutecznego zerwania kontraktu / niezmienników nadklasy z podklas).
Adam Hošek

Odpowiedzi:

18

superklasa jest inicjowana przed podklasą.

Wywołanie konstruktora B wywołuje konstruktor A, który wywołuje funkcję f wypisującą „x in f: 1”, po zainicjowaniu A reszta B jest inicjalizowana.

Zasadniczo ustawienie wartości jest zastępowane.

(Gdy inicjujesz prymitywy z ich zerową wartością w Kotlinie, technicznie po prostu wcale się nie inicjują)

Możesz zaobserwować to zachowanie „nadpisujące”, zmieniając podpis z

var x: Int = 0 do var x: Int? = 0

Ponieważ xnie jest już prymitywne int, pole faktycznie zostaje zainicjowane na wartość, co daje wynik:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
źródło
5
Kiedy inicjujesz prymitywy z ich zerową wartością w Kotlinie, technicznie po prostu wcale nie inicjują tego, co chciałem przeczytać ... Dzięki!
deHaar
To nadal wydaje się błędem / niespójnością.
Kroppeb,
2
@Kroppeb to tylko Java, to samo zachowanie można zaobserwować w samym kodzie Java. Nie ma to nic wspólnego z
Kotlinem
8

To zachowanie jest opisane w dokumentacji - https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

Jeśli którakolwiek z tych właściwości zostanie użyta w logice inicjalizacji klasy podstawowej (bezpośrednio lub pośrednio, przez inną zastąpioną implementację otwartego elementu), może to prowadzić do niepoprawnego zachowania lub awarii środowiska wykonawczego. Projektując klasę podstawową, należy unikać używania otwartych elementów w konstruktorach, inicjalizatorach właściwości i blokach init.

UPD:

Istnieje błąd, który powoduje tę niespójność - https://youtrack.jetbrains.com/issue/KT-15642

Gdy właściwość jest przypisana jako efekt uboczny wywołania funkcji wirtualnej wewnątrz superkonstruktora, jej inicjalizator nie zastępuje właściwości, jeśli wyrażenie inicjujące ma domyślną wartość typu (null, pierwotna zero).

vanyochek
źródło
1
Ponadto IntelliJ ostrzega o tym. Wywołanie f()w initbloku Aostrzeżenia „Wywoływanie funkcji niefunkcyjnej f w konstruktorze”
Kroppeb,
W dostarczonej dokumentacji napisano, że „inicjalizacja klasy podstawowej odbywa się jako pierwszy krok, a zatem dzieje się przed uruchomieniem logiki inicjalizacji klasy pochodnej”, co dokładnie dzieje się w pierwszym przykładzie pytania. Jednak w drugim przykładzie instrukcja inicjalizacji ( var x: Int = 0) klasy pochodnej nie jest wcale uruchamiana, co jest sprzeczne z tym, co mówi dokumentacja, co prowadzi mnie do przekonania, że ​​może to być błąd.
Subaru Tashiro
@SubaruTashiro Tak, masz rację. To kolejny problem - youtrack.jetbrains.com/issue/KT-15642 .
vanyochek