A inicialização de variável Kotlin para classe filho se comporta de forma estranha ao inicializar variável com valor 0

16

Eu criei a seguinte hierarquia de classes:

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

A saída deste código é

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

Mas se eu mudar a inicialização xdo

var x: Int = 33

para

var x: Int = 0

a saída mostra a invocação do método em contraste com a saída acima:

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

Alguém sabe por que a inicialização com 0causa um comportamento diferente daquele com outro valor?

PRATYUSH SINGH
fonte
4
Não está diretamente relacionado, mas chamar métodos substituíveis de construtores geralmente não é uma boa prática, pois pode levar a um comportamento inesperado (e quebrar efetivamente o contrato / invariantes da superclasse das subclasses).
Adam Hošek 20/01

Respostas:

18

superclasse é inicializada antes da subclasse.

A chamada do construtor de B chama o construtor de A, que chama a função f imprimindo "x em f: 1", depois que A é inicializado, o restante de B é inicializado.

Então, essencialmente, a configuração do valor está sendo substituída.

(Quando você inicializa as primitivas com seu valor zero no Kotlin, elas tecnicamente simplesmente não são inicializadas)

Você pode observar esse comportamento de "substituição" alterando a assinatura de

var x: Int = 0 para var x: Int? = 0

Como xnão é mais a primitiva int, o campo é realmente inicializado com um valor, produzindo a saída:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
fonte
5
Quando você inicializa primitivas com seu valor zero no Kotlin, tecnicamente elas simplesmente não inicializam nada, é o que eu queria ler ... Obrigado!
deHaar 20/01
Isso ainda parece um bug / inconsistência.
Kroppeb 20/01
2
@ Kroppeb este é apenas Java, o mesmo comportamento pode ser observado apenas no código Java. Não tem nada a ver com Kotlin
Sxtanna 20/01
8

Esse comportamento é descrito na documentação - https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

Se alguma dessas propriedades for usada na lógica de inicialização da classe base (direta ou indiretamente, por meio de outra implementação de membro aberto substituída), isso poderá levar a um comportamento incorreto ou a uma falha no tempo de execução. Ao projetar uma classe base, você deve evitar o uso de membros abertos nos construtores, inicializadores de propriedades e blocos de inicialização.

UPD:

Há um erro que produz essa inconsistência - https://youtrack.jetbrains.com/issue/KT-15642

Quando uma propriedade é atribuída como efeito colateral de uma chamada de função virtual dentro do super construtor, seu inicializador não substitui a propriedade se a expressão do inicializador for o valor padrão do tipo (nulo, zero primitivo).

vanyochek
fonte
11
Além disso, o IntelliJ alerta sobre isso. A chamada f()no initbloco de Adá o aviso "Chamando a função não final f no construtor"
Kroppeb 20/01
Na documentação que você forneceu, diz "a inicialização da classe base é feita como a primeira etapa e, portanto, acontece antes da execução da lógica de inicialização da classe derivada", que é exatamente o que acontece no primeiro exemplo da pergunta. No entanto, no segundo exemplo, a instrução de inicialização ( var x: Int = 0) da classe derivada não é executada, o que é contrário ao que a documentação diz, o que me leva a acreditar que isso pode ser um bug.
Subaru Tashiro
@SubaruTashiro Sim, você está certo. É outra questão - youtrack.jetbrains.com/issue/KT-15642 .
vanyochek 21/01