web-dev-qa-db-fra.com

En Kotlin, quel est le moyen idiomatique de traiter les valeurs nullables, de les référencer ou de les convertir

Si j'ai un type nullable Xyz?, je veux le référencer ou le convertir en un type non nullable Xyz. Quelle est la manière idiomatique de le faire à Kotlin?

Par exemple, ce code est en erreur:

val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"

Mais si je vérifie d'abord null, cela est autorisé, pourquoi?

val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
    something.foo() 
}

Comment puis-je modifier ou traiter une valeur comme non null sans exiger la vérification if, en supposant que je sache avec certitude qu'il ne s'agit vraiment jamais de null? Par exemple, je récupère ici une valeur d’une carte dont je peux garantir l’existence et le résultat de get() n’est pas null. Mais j'ai une erreur:

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")
something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?"

La méthode get() pense qu'il est possible que l'élément soit manquant et renvoie le type Int?. Par conséquent, quel est le meilleur moyen de forcer le type de la valeur à ne pas être annulable?

Remarque: cette question est intentionnellement écrite et répondue par l'auteur ( Questions à réponse spontanée ) , de sorte que les réponses idiomatiques aux questions fréquemment posées de Kotlin soient présentes dans SO. Également pour clarifier certaines très vieilles réponses écrites pour les alphas de Kotlin qui ne sont pas exactes pour le Kotlin actuel.

149
Jayson Minard

Tout d’abord, vous devriez tout lire à propos de Null Safety dans Kotlin, qui couvre bien les cas.

Dans Kotlin, vous ne pouvez pas accéder à une valeur nullable sans être sûr que ce n'est pas null ( Vérification de la nullité dans les conditions ), ou en affirmant que ce n'est sûrement pas null en utilisant _!!_ opérateur sûr , y accéder avec un _?._ appel sécurisé , ou enfin donner à quelque chose qui est probablement null une valeur par défaut en utilisant _?:_ opérateur Elvis .

Pour votre 1er cas dans votre question vous avez des options dépendant de l'intention du code, vous utiliserez l'une d'entre elles, et elles sont toutes idiomatiques mais ont des résultats différents :

_val something: Xyz? = createPossiblyNullXyz()

// access it as non-null asserting that with a sure call
val result1 = something!!.foo()

// access it only if it is not null using safe operator, 
// returning null otherwise
val result2 = something?.foo()

// access it only if it is not null using safe operator, 
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue

// null check it with `if` expression and then use the value, 
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
                   something.foo() 
              } else { 
                   ...
                   differentValue 
              }

// null check it with `if` statement doing a different action
if (something != null) { 
    something.foo() 
} else { 
    someOtherAction() 
}
_

Pour le "Pourquoi ça marche quand la case est cochée", lisez les informations de base ci-dessous sur smart castts .

Pour votre deuxième cas dans votre question dans la question avec Map, si vous en tant que développeur êtes certain que le résultat ne sera jamais null, utilisez _!!_ opérateur sûr comme une affirmation:

_val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
_

ou dans un autre cas, lorsque la carte POURRAIT renvoyer une valeur null mais que vous pouvez fournir une valeur par défaut, alors Map a lui-même une méthode getOrElse :

_val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid
_

Informations d'arrière-plan:

Remarque: Dans les exemples ci-dessous, j'utilise des types explicites pour clarifier le comportement. Avec l'inférence de type, les types peuvent normalement être omis pour les variables locales et les membres privés.

En savoir plus sur l'opérateur _!!_ sure

L'opérateur _!!_ affirme que la valeur n'est pas null ou renvoie un NPE. Cela devrait être utilisé dans les cas où le développeur garantit que la valeur ne sera jamais null. Pensez-y comme une assertion suivie d'un smart cast .

_val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!! 
// same thing but access members after the assertion is made:
possibleXyz!!.foo()
_

en savoir plus: !! opérateur sûr


En savoir plus sur null Vérification et projections intelligentes

Si vous protégez l'accès à un type nullable avec une vérification null, le compilateur va smart cast la valeur dans le corps de l'instruction n'est pas nullable. Cela ne peut se produire dans certains flux complexes, mais cela fonctionne très bien dans les cas courants.

_val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}
_

Ou si vous effectuez une vérification is pour un type non nullable:

_if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}
_

Et la même chose pour les expressions 'quand' qui sont également sécuritaires:

_when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim() 
}
_

Certaines choses n'autorisent pas le contrôle null à smart cast pour une utilisation ultérieure de la variable. L'exemple ci-dessus utilise une variable locale qui ne peut en aucune manière avoir muté dans le flux de l'application, que ce soit val ou var, cette variable n'a pas eu la possibilité de muter en un null. Mais, dans les autres cas où le compilateur ne peut pas garantir l'analyse des flux, il s'agirait d'une erreur:

_var nullableInt: Int? = ...

public fun foo() {
    if (nullableInt != null) {
        // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
        val nonNullableInt: Int = nullableInt
    }
}
_

Le cycle de vie de la variable nullableInt n'est pas complètement visible et peut être attribué à partir d'autres threads. La vérification null ne peut pas être distribution intelligente dans une valeur non Nullable. Consultez la rubrique "Appels sécurisés" ci-dessous pour une solution de contournement.

Un autre cas qui ne peut pas être approuvé par un distribution intelligente pour ne pas muter est une propriété val sur un objet doté d'un getter personnalisé. Dans ce cas, le compilateur n'a aucune visibilité sur ce qui mute la valeur et vous obtiendrez donc un message d'erreur:

_class MyThing {
    val possibleXyz: Xyz? 
        get() { ... }
}

// now when referencing this class...

val thing = MyThing()
if (thing.possibleXyz != null) {
   // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}
_

en savoir plus: Vérification de la nullité dans les conditions


En savoir plus sur l'opérateur _?._ Safe Call

L'opérateur d'appels sécurisés renvoie null si la valeur à gauche est null, sinon continue à évaluer l'expression à droite.

_val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
_

Voici un autre exemple où vous souhaitez parcourir une liste, mais uniquement si ce n'est pas null et pas vide, l'opérateur de sécurité est à nouveau utile:

_val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
_

Dans l'un des exemples ci-dessus, nous avons eu un cas où nous avons effectué une vérification if mais que nous avons la chance qu'un autre thread mue la valeur et donc non smart cast . Nous pouvons changer cet exemple pour utiliser l'opérateur d'appel sécurisé avec la fonction let pour résoudre ceci:

_var possibleXyz: Xyz? = 1

public fun foo() {
    possibleXyz?.let { value ->
        // only called if not null, and the value is captured by the lambda
        val surelyXyz: Xyz = value
    }
}
_

en savoir plus: Appels sécurisés


En savoir plus sur le _?:_ Elvis Operator

L'opérateur Elvis vous permet de fournir une valeur alternative lorsqu'une expression à gauche de l'opérateur est null:

_val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
_

Il a également quelques utilisations créatives, par exemple, déclenche une exception lorsque quelque chose est null:

_val currentUser = session.user ?: throw Http401Error("Unauthorized")
_

ou pour revenir tôt d'une fonction:

_fun foo(key: String): Int {
   val startingCode: String = codes.findKey(key) ?: return 0
   // ...
   return endingValue
}
_

en savoir plus: opérateur Elvis


Opérateurs nuls avec des fonctions connexes

Kotlin stdlib a une série de fonctions qui fonctionnent vraiment bien avec les opérateurs mentionnés ci-dessus. Par exemple:

_// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething

// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
    func1()
    func2()
}

// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName

val something = name.takeUnless { it.isBlank() } ?: defaultName
_

Rubriques connexes

Dans Kotlin, la plupart des applications tentent d'éviter les valeurs null, mais ce n'est pas toujours possible. Et parfois, null est parfaitement logique. Quelques lignes directrices à considérer:

  • dans certains cas, différents types de retour sont garantis, notamment le statut de l'appel de la méthode et le résultat obtenu en cas de succès. Des bibliothèques telles que Résultat vous fournissent un type de résultat de succès ou d'échec pouvant également créer une branche pour votre code. Et la bibliothèque de promesses pour Kotlin appelée Kovenant fait la même chose sous forme de promesses.

  • pour les collections en tant que types de retour, retourne toujours une collection vide au lieu de null, sauf si vous avez besoin d'un troisième état "non présent". Kotlin dispose de fonctions d'assistance telles que emptyList() OU emptySet() pour créer ces valeurs vides.

  • lorsque vous utilisez des méthodes qui renvoient une valeur nullable pour laquelle vous avez une valeur par défaut ou une alternative, utilisez l'opérateur Elvis pour fournir une valeur par défaut. Dans le cas d'un Map, utilisez getOrElse() , qui permet de générer une valeur par défaut à la place de la méthode Mapget(), qui renvoie une valeur nullable. Idem pour getOrPut()

  • lors de la substitution de méthodes de Java où Kotlin n’est pas sûr de la nullabilité du code Java, vous pouvez toujours supprimer la nullité de _?_ de votre substitution si vous êtes sûr de ce que la signature et la fonctionnalité devraient être. Par conséquent, votre méthode surchargée est davantage null sûre. Pareil pour l'implémentation des interfaces Java dans Kotlin, changez la valeur NULL en ce que vous savez être valide.

  • examinez les fonctions qui peuvent déjà aider, comme pour String?.isNullOrEmpty() et String?.isNullOrBlank() , qui peuvent fonctionner en toute sécurité sur une valeur nullable et faire ce que vous attendez. En fait, vous pouvez ajouter vos propres extensions pour combler les lacunes de la bibliothèque standard.

  • fonctions d'assertion telles que checkNotNull() et requireNotNull() dans la bibliothèque standard.

  • des fonctions d'assistance telles que filterNotNull() qui suppriment les valeurs NULL des collections, ou listOfNotNull() pour renvoyer une liste d'éléments nuls ou uniques à partir éventuellement d'une valeur null.

  • il existe également un opérateur Safe (nullable) qui permet de transtyper vers un type non nullable et renvoie la valeur null si ce n'est pas possible. Mais je n'ai pas de cas d'utilisation valable pour cela qui n'est pas résolu par les autres méthodes mentionnées ci-dessus.

266
Jayson Minard

La réponse précédente est difficile à suivre, mais voici un moyen simple et rapide:

val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo() 

Si ce n'est vraiment jamais nul, l'exception ne se produira pas, mais si c'est toujours le cas, vous verrez ce qui ne va pas.

2
John Farrell