La inicialización de la variable Kotlin para la clase secundaria se comporta de forma extraña para inicializar la variable con valor 0

16

He creado la siguiente jerarquía de clases:

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

La salida de este código es

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

Pero si cambio de la inicialización de xpartir

var x: Int = 33

a

var x: Int = 0

el resultado muestra la invocación del método en contraste con el resultado anterior:

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

¿Alguien sabe por qué la inicialización con 0causa un comportamiento diferente al de otro valor?

PRATYUSH SINGH
fuente
44
No está directamente relacionado, pero llamar a los métodos reemplazables de los constructores generalmente no es una buena práctica, ya que puede conducir a un comportamiento inesperado (y romper efectivamente el contrato / invariantes de la superclase de las subclases).
Adam Hošek

Respuestas:

18

La superclase se inicializa antes de la subclase.

La llamada al constructor de B llama al constructor de A, que llama a la función f imprimiendo "x en f: 1", después de que A se inicializa, el resto de B se inicializa.

Entonces, esencialmente, la configuración del valor se sobrescribe.

(Cuando inicializa primitivas con su valor cero en Kotlin, técnicamente no se inicializan en absoluto)

Puede observar este comportamiento de "sobrescritura" cambiando la firma de

var x: Int = 0 a var x: Int? = 0

Como xya no es la primitiva int, el campo en realidad se inicializa a un valor, produciendo la salida:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
fuente
55
Cuando inicializas primitivas con su valor cero en Kotlin, técnicamente simplemente no se inicializan, es lo que quería leer ... ¡Gracias!
deHaar
Esto todavía parece un error / inconsistencia.
Kroppeb
2
@Kroppeb esto es solo Java, el mismo comportamiento se puede observar solo en el código Java. No tiene nada que ver con Kotlin
Sxtanna
8

Este comportamiento se describe en la documentación: https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

Si alguna de esas propiedades se usa en la lógica de inicialización de la clase base (ya sea directa o indirectamente, a través de otra implementación de miembro abierto anulada), puede provocar un comportamiento incorrecto o una falla en el tiempo de ejecución. Al diseñar una clase base, debe evitar el uso de miembros abiertos en los constructores, inicializadores de propiedades y bloques de inicio.

UPD:

Hay un error que produce esta inconsistencia: https://youtrack.jetbrains.com/issue/KT-15642

Cuando se asigna una propiedad como efecto secundario de una llamada de función virtual dentro del superconstructor, su inicializador no sobrescribe la propiedad si la expresión del inicializador es el valor predeterminado de tipo (nulo, cero primitivo).

vanyochek
fuente
1
Además, IntelliJ te advierte al respecto. Llamar f()en el initbloque de Ada la advertencia "Llamar a la función no final f en el constructor"
Kroppeb
En la documentación que proporcionó, dice "la inicialización de la clase base se realiza como el primer paso y, por lo tanto, ocurre antes de que se ejecute la lógica de inicialización de la clase derivada", que es exactamente lo que sucede en el primer ejemplo de la pregunta. Sin embargo, en el segundo ejemplo, la instrucción de inicialización ( var x: Int = 0) de la clase derivada no se ejecuta en absoluto, lo que es contrario a lo que dice la documentación, lo que me lleva a creer que esto podría ser un error.
Subaru Tashiro
@SubaruTashiro Sí, tienes razón. Es otro problema: youtrack.jetbrains.com/issue/KT-15642 .
vanyochek