Die Initialisierung der Kotlin-Variablen für die untergeordnete Klasse verhält sich seltsam, wenn die Variable mit dem Wert 0 initialisiert wird

16

Ich habe die folgende Klassenhierarchie erstellt:

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

Die Ausgabe dieses Codes ist

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

Aber wenn ich die Initialisierung von xvon ändere

var x: Int = 33

zu

var x: Int = 0

Die Ausgabe zeigt den Aufruf der Methode im Gegensatz zur obigen Ausgabe:

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

Weiß jemand, warum die Initialisierung mit 0ein anderes Verhalten verursacht als die mit einem anderen Wert?

PRATYUSH SINGH
quelle
4
Nicht direkt verwandt, aber das Aufrufen überschreibbarer Methoden von Konstruktoren ist im Allgemeinen keine gute Praxis, da dies zu unerwartetem Verhalten führen kann (und den Oberklassenvertrag / die Invarianten aus Unterklassen effektiv brechen kann).
Adam Hošek

Antworten:

18

Die Superklasse wird vor der Unterklasse initialisiert.

Der Konstruktoraufruf von B ruft den Konstruktor von A auf, der die Funktion f aufruft, die "x in f: 1" druckt, nachdem A initialisiert wurde, wird der Rest von B initialisiert.

Im Wesentlichen wird die Einstellung des Werts überschrieben.

(Wenn Sie Grundelemente mit ihrem Nullwert in Kotlin initialisieren, werden sie technisch überhaupt nicht initialisiert.)

Sie können dieses "Überschreiben" -Verhalten beobachten, indem Sie die Signatur von ändern

var x: Int = 0 zu var x: Int? = 0

Da xes nicht mehr das Grundelement ist int, wird das Feld tatsächlich auf einen Wert initialisiert, wodurch die Ausgabe erzeugt wird:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
quelle
5
Wenn Sie Primitive mit ihrem Nullwert in Kotlin initialisieren, werden sie technisch gesehen überhaupt nicht initialisiert, was ich lesen wollte ... Danke!
deHaar
Dies scheint immer noch ein Fehler / eine Inkonsistenz zu sein.
Kroppeb
2
@Kroppeb dies ist nur Java, das gleiche Verhalten kann nur in Java-Code beobachtet werden. Es hat nichts mit Kotlin zu tun
Sxtanna
8

Dieses Verhalten wird in der Dokumentation beschrieben - https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

Wenn eine dieser Eigenschaften in der Initialisierungslogik der Basisklasse verwendet wird (entweder direkt oder indirekt über eine andere überschriebene Open-Member-Implementierung), kann dies zu falschem Verhalten oder einem Laufzeitfehler führen. Wenn Sie eine Basisklasse entwerfen, sollten Sie daher vermeiden, offene Elemente in den Konstruktoren, Eigenschaftsinitialisierern und Init-Blöcken zu verwenden.

UPD:

Es gibt einen Fehler, der diese Inkonsistenz verursacht - https://youtrack.jetbrains.com/issue/KT-15642

Wenn eine Eigenschaft als Nebeneffekt eines virtuellen Funktionsaufrufs im Superkonstruktor zugewiesen wird, überschreibt der Initialisierer die Eigenschaft nicht, wenn der Initialisiererausdruck der Standardwert vom Typ (null, primitiv null) ist.

Vanyochek
quelle
1
Darüber hinaus warnt Sie IntelliJ davor. Das Aufrufen f()des initBlocks von Agibt die Warnung "Aufrufen der nicht endgültigen Funktion f im Konstruktor"
Kroppeb
In der von Ihnen bereitgestellten Dokumentation heißt es: "Die Initialisierung der Basisklasse erfolgt als erster Schritt und erfolgt daher, bevor die Initialisierungslogik der abgeleiteten Klasse ausgeführt wird." Genau dies geschieht im ersten Beispiel in der Frage. Im zweiten Beispiel wird die Initialisierungsanweisung ( var x: Int = 0) der abgeleiteten Klasse jedoch überhaupt nicht ausgeführt, was im Widerspruch zu den Angaben in der Dokumentation steht, was mich zu der Annahme veranlasst, dass dies ein Fehler sein könnte.
Subaru Tashiro
@SubaruTashiro Ja, du hast recht. Es ist ein weiteres Problem - youtrack.jetbrains.com/issue/KT-15642 .
Vanyochek