Var nullable e smart cast

Aug 30 2020

Considera il seguente blocco di Kotlin.

var nullableInt: Int? = null

if (nullableInt != null) {
    val checkedInt: Int = nullableInt
    print("not-null-branch")
} else {
    print("null-branch")
}

Android Studio mi dice che il cast intelligente da Int?a Intnon è possibile, poiché nullableIntè mutevole. Capisco che questo potrebbe essere un problema nel codice multithread.

Un modo per gestire il problema è fare un cast esplicito con val checkedInt: Int = nullableInt!!, ma se usassi il codice in un ambiente multithread non è consigliabile.


Chiudi i duplicati

Ci sono un paio di domande molto vicine su SO riguardo a questo argomento. Tuttavia, non trovo una risposta soddisfacente in nessuno di quelli che ho trovato:

In Kotlin, qual è il modo idiomatico di gestire valori nullable, fare riferimento o convertirli discute perché si pone il problema, ma non fornisce suggerimenti su come gestirlo

Kotlin evita lo smart cast per il controllo null ha un ramo if-not-null che restituisce un valore non null, quindi il ?.let{} :? {}costrutto funziona lì. Poiché il mio ramo non nullo restituisce null, verranno eseguiti entrambi i rami.

Kotlin "Smart cast è impossibile, perché la proprietà avrebbe potuto essere cambiata in questo momento" riguarda solo un ramo non nullo e nessun ramo nullo, quindi il ?.let{}costrutto sembra corretto. In quel thread, forniscono il suggerimento di prendere una copia locale prima della ifdichiarazione, il che potrebbe essere fattibile anche nel mio caso. Purtroppo non è molto elegante e spero che ci sia qualche altra alternativa.


Esiste un modo per gestire questa ramificazione condizionale nullo in modo nullo senza prendere una copia?

Capisco che la risposta potenzialmente potrebbe essere "dipende". Se è così, per favore dillo e spiega perché.

Risposte

3 Sam Aug 30 2020 at 11:35

Usa .letinvece di?.let

Poiché la .letfunzione di estensione è definita per tutti i tipi, inclusi quelli nullable, è possibile chiamarla effettivamente senza l' ?.operatore di chiamata sicura . Quando lo fai, il lambda verrà sempre chiamato, anche per i nullvalori. Il parametro all'interno del letblocco sarà nullable se il ricevitore è nullable.

Tuttavia, il parametro lambda è idoneo per lo smart casting , perché non è modificabile.

Ecco la differenza:

x.let { it -> /* This always runs. 'it' can be null if 'x' is null */ }
x?.let { it -> /* This only runs if 'x' is not null. 'it' is never null. */ }

Applicandolo al tuo esempio, potresti scrivere questo:

var nullableInt: Int? = null

nullableInt.let {
    if (it != null) {
         doSomethingWith(it)
    } else {
         doSomethingElse()
    }
}
2 Tenfour04 Aug 30 2020 at 12:57

Non è possibile avviare il cast di una proprietà perché un altro thread potrebbe modificarla. Non c'è un modo logico per aggirare questo. La lingua fornisce già un modo non sicuro per farlo con ciò !!che hai già menzionato.

Fare una copia locale del riferimento (manualmente o utilizzando una funzione di ambito come witho let) è banale, quindi non è qualcosa di cui ti devi preoccupare.

La mia opinione personale è che una variabile locale è il modo più pulito e leggibile per farlo. Eviti l'annidamento di blocchi che avresti con le funzioni di ambito.

val myVar = myProp
if (myVar != null) {

} else {

}

Penso che il modo più pulito con le funzioni di ambito sia quello di utilizzare with. Legge meglio di letquando gestisci entrambi i rami.

with(myProp) {
    if (this != null) {

    } else {

    }
}

Per un modo conciso di gestire entrambi i rami, funziona quanto segue. Considero alsoun po 'più robusto rispetto leta questa situazione, perché non puoi eseguire accidentalmente entrambi i rami restituendo un valore nullo dal primo lambda. Ma la leggibilità soffre di andare così conciso.

myProp?.also {
    // not null it
} ?: run {
    // null
}