Var Nullable et Smart Cast

Aug 30 2020

Considérez le bloc suivant de Kotlin.

var nullableInt: Int? = null

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

Android Studio me dit que la distribution intelligente de Int?à Intn'est pas possible, car elle nullableIntest mutable. Je comprends que cela peut être un problème dans le code multithread.

Une façon de gérer le problème est de faire un cast explicite avec val checkedInt: Int = nullableInt!!, mais si j'utiliserais le codé dans un environnement multithread, ce n'est pas conseillé.


Fermer les doublons

Il y a quelques questions très proches sur SO concernant ce sujet. Cependant, je ne trouve de réponse satisfaisante dans aucune de celles que j'ai trouvées:

Dans Kotlin, quelle est la manière idiomatique de traiter les valeurs nullables, de les référencer ou de les convertir, explique pourquoi le problème se pose, mais ne fournit aucune suggestion sur la façon de le gérer

Kotlin évite le cast intelligent pour la vérification de null a une branche if-not-null qui retourne une valeur non nulle, donc la ?.let{} :? {}construction fonctionne là. Étant donné que ma branche non nulle renvoie null, les deux branches s'exécuteraient.

Kotlin "La distribution intelligente est impossible, car la propriété aurait pu être modifiée à ce moment-là" concerne uniquement une branche non nulle et aucune branche nulle, donc la ?.let{}construction semble correcte. Dans ce fil, ils suggèrent de prendre une copie locale avant la ifdéclaration, ce qui pourrait être faisable dans mon cas également. Ce n'est malheureusement pas très élégant, et j'espère qu'il existe une autre alternative.


Existe-t-il un moyen de gérer ce branchement conditionnel nul de manière sûre sans prendre de copie?

Je comprends que la réponse pourrait être «cela dépend». Si tel est le cas, veuillez le dire et expliquer pourquoi.

Réponses

3 Sam Aug 30 2020 at 11:35

Utiliser à la .letplace de?.let

Étant donné que la .letfonction d'extension est définie pour tous les types, y compris les types Nullables, vous pouvez en fait l'appeler sans l' ?.opérateur d' appel sécurisé . Lorsque vous faites cela, le lambda sera toujours appelé, même pour les nullvaleurs. Le paramètre à l'intérieur du letbloc sera nullable si le récepteur est nullable.

Cependant, le paramètre lambda est éligible pour la diffusion intelligente , car il n'est pas modifiable.

Voici la différence:

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. */ }

En appliquant cela à votre exemple, vous pourriez écrire ceci:

var nullableInt: Int? = null

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

Vous ne pouvez pas lancer le cast d'une propriété car un autre thread pourrait la modifier. Il n'y a aucun moyen logique de contourner cela. La langue fournit déjà un moyen dangereux de le faire avec le !!que vous avez déjà mentionné.

Faire une copie locale de la référence (manuellement ou en utilisant une fonction de portée comme withou let) est trivial, donc ce n'est pas quelque chose dont vous devez vous soucier.

Mon opinion personnelle est qu'une variable locale est le moyen le plus propre et le plus lisible de le faire. Vous évitez l'imbrication de blocs que vous auriez avec les fonctions de portée.

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

} else {

}

Je pense que le moyen le plus propre avec les fonctions d'oscilloscope est d'utiliser with. Il lit mieux que letlorsque vous gérez les deux branches.

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

    } else {

    }
}

Pour une manière concise de gérer les deux branches, ce qui suit fonctionne. Je considère que alsoc'est un peu plus robuste que letpour cette situation, car vous ne pouvez pas exécuter accidentellement les deux branches en renvoyant un null du premier lambda. Mais la lisibilité souffre d'être aussi concis.

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