Java >> Java tutorial >  >> Tag >> final

Kotlin kalder ikke endelig funktion i konstruktørværker

Initialiseringsrækkefølgen for en afledt klasse er beskrevet i sprogreferencen:Afledt klasseinitialiseringsrækkefølge, og afsnittet forklarer også, hvorfor det er en dårlig (og potentielt farlig) praksis at bruge et åbent medlem i initialiseringslogikken for din klasse.

Dybest set på det tidspunkt, hvor en superklassekonstruktør (inklusive dens egenskabsinitialiserere og init blokke) er udført, er den afledte klassekonstruktør endnu ikke kørt. Men de tilsidesatte medlemmer bevarer deres logik, selv når de kaldes fra superklassekonstruktøren. Dette kan føre til, at et tilsidesat medlem, der er afhængig af en tilstand, der er specifik for den afledte klasse, kaldes fra superkonstruktøren, hvilket kan føre til en fejl eller en runtime-fejl. Dette er også et af tilfældene, hvor du kan få en NullPointerException i Kotlin.

Overvej dette kodeeksempel:

open class Base {
    open val size: Int = 0
    init { println("size = $size") }
}

class Derived : Base() {
    val items = mutableListOf(1, 2, 3)
    override val size: Int get() = items.size
}

(kørbar prøve)

Her er den tilsidesatte size er afhængig af items bliver korrekt initialiseret, men på det tidspunkt, hvor size bruges i superkonstruktøren, backing-feltet for items er stadig nul. Konstruerer en instans af Derived kaster derfor en NPE.

At bruge den pågældende praksis sikkert kræver betydelig indsats, selv når du ikke deler koden med andre, og når du gør det, vil andre programmører normalt forvente, at åbne medlemmer er sikre at tilsidesætte, der involverer den afledte klasses tilstand.

Som @Bob Dagleish korrekt bemærkede, kan du bruge doven initialisering til code ejendom:

val code by lazy { calculate() }

Men så skal du være forsigtig og ikke bruge code andre steder i basisklassens konstruktionslogik.

En anden mulighed er at kræve code skal videregives til basisklassekonstruktøren:

abstract class Base(var code: Int) {
    abstract fun calculate(): Int
}

class Derived(private val x: Int) : Base(calculateFromX(x)) {
    override fun calculate(): Int = 
        calculateFromX(x)

    companion object {
        fun calculateFromX(x: Int) = x
    }
}

Dette komplicerer imidlertid koden for de afledte klasser i tilfælde, hvor den samme logik bruges både i tilsidesatte medlemmer og til at beregne de værdier, der sendes til superkonstruktøren.


Det er bestemt dårlig praksis, fordi du påberåber dig calculate() på et delvist konstrueret objekt . Dette tyder på, at din klasse har flere faser af initialisering.

Hvis resultatet af calculation() bruges til at initialisere et medlem eller udføre layout eller noget, kan du overveje at bruge doven initialisering . Dette vil udsætte beregningen af ​​resultatet, indtil resultatet virkelig er nødvendigt.


Java tag