
Opérateur d'appel sécurisé : `?.`
Apprenez à utiliser l'opérateur d'appel sécurisé `?.` en Kotlin pour accéder aux propriétés ou méthodes d'une variable nullable en toute sécurité, évitant les NPE.
Le défi : accéder aux membres d'une variable nullable
Nous avons vu que le compilateur Kotlin interdit l'accès direct aux membres (propriétés ou méthodes) d'une variable déclarée comme nullable (avec `?`) car elle pourrait contenir `null`, ce qui entraînerait une `NullPointerException` (NPE). Par exemple, si `maybeString: String?` est `null`, tenter `maybeString.length` échouerait.
Alors, comment peut-on travailler avec ces variables nullables de manière sûre ? La méthode traditionnelle serait d'utiliser une condition `if` explicite :
fun getLengthIfNotNull(maybeString: String?): Int? {
if (maybeString != null) {
return maybeString.length // OK ici, car Kotlin sait que maybeString n'est pas null dans ce bloc
} else {
return null
}
}
fun main() {
val s1: String? = "CertiQuizz"
val s2: String? = null
println(getLengthIfNotNull(s1)) // Affiche 10
println(getLengthIfNotNull(s2)) // Affiche null
}Bien que cette approche fonctionne et soit parfaitement valide (et parfois nécessaire pour des logiques plus complexes), elle peut devenir rapidement verbeuse si vous devez enchaîner plusieurs accès ou effectuer des vérifications répétées. Kotlin propose une solution beaucoup plus élégante et concise : l'opérateur d'appel sécurisé, ou safe call operator.
Présentation de l'opérateur `?.` (Safe Call)
L'opérateur d'appel sécurisé s'écrit `?.`. Il se place entre la référence nullable et le membre (propriété ou méthode) auquel vous souhaitez accéder.
Son fonctionnement est simple et élégant :
- Si la référence à gauche de `?.` (la variable nullable) n'est pas `null`, alors l'accès au membre (à droite de `?.`) est effectué normalement, comme si la référence était non-nullable.
- Si la référence à gauche de `?.` est `null`, alors l'accès au membre n'est pas tenté du tout, et l'ensemble de l'expression `reference?.membre` évalue immédiatement à `null`.
Cet opérateur court-circuite l'appel si la référence est nulle, évitant ainsi toute possibilité de NPE.
fun main() {
val name: String? = "Kotlin"
val emptyName: String? = null
// Utilisation de l'appel sécurisé pour accéder à la propriété 'length'
val length1: Int? = name?.length
val length2: Int? = emptyName?.length
println("Longueur de '$name': $length1") // Affiche: Longueur de 'Kotlin': 6
println("Longueur de 'null': $length2") // Affiche: Longueur de 'null': null
// Utilisation pour appeler une méthode
val upper1: String? = name?.uppercase() // 'name' n'est pas null, uppercase() est appelée
val upper2: String? = emptyName?.uppercase() // 'emptyName' est null, uppercase() n'est PAS appelée, résultat = null
println("Majuscules de '$name': $upper1") // Affiche: Majuscules de 'Kotlin': KOTLIN
println("Majuscules de 'null': $upper2") // Affiche: Majuscules de 'null': null
}Le type de retour d'un appel sécurisé est toujours nullable
Une conséquence importante de l'utilisation de `?.` est que le type de retour de l'expression entière `reference?.membre` est toujours un type nullable, même si le membre lui-même (par exemple, la propriété `length` d'une `String` ou la méthode `uppercase()`) retourne normalement un type non-nullable.
Pourquoi ? Parce que si la référence initiale est `null`, l'expression entière retourne `null`. Donc, le type résultant doit pouvoir représenter cette possibilité.
fun main() {
val s: String? = "Example"
// s.length retourne Int (non-nullable) si s n'est pas null
// mais s?.length retourne Int? (nullable)
val len: Int? = s?.length
// s.hashCode() retourne Int (non-nullable) si s n'est pas null
// mais s?.hashCode() retourne Int? (nullable)
val hc: Int? = s?.hashCode()
println(len)
println(hc)
}Gardez cela à l'esprit : l'utilisation de `?.` propage la nullabilité dans le type de retour de l'expression.
Chaînage des appels sécurisés
La vraie puissance de l'opérateur `?.` se révèle lorsque vous devez accéder à des membres de membres, où chaque étape de la chaîne pourrait potentiellement être `null`. Vous pouvez simplement chaîner les appels sécurisés.
Considérez une structure comme `user?.address?.street?.name`. Si `user` est `null`, toute l'expression devient `null`. Si `user` n'est pas `null` mais `user.address` est `null`, l'expression devient `null`. Si `user` et `user.address` ne sont pas `null` mais `user.address.street` est `null`, l'expression devient `null`, et ainsi de suite. L'accès à `.name` ne sera tenté que si toutes les références précédentes (`user`, `address`, `street`) sont non nulles.
data class Street(val name: String?)
data class Address(val street: Street?)
data class User(val name: String, val address: Address?)
fun main() {
val user1 = User("Alice", Address(Street("Main St")))
val user2 = User("Bob", Address(null)) // Pas de rue
val user3 = User("Charlie", null) // Pas d'adresse
val user4: User? = null // Utilisateur potentiellement null
// Chaînage sécurisé pour obtenir le nom de la rue
val streetName1: String? = user1?.address?.street?.name
val streetName2: String? = user2?.address?.street?.name
val streetName3: String? = user3?.address?.street?.name
val streetName4: String? = user4?.address?.street?.name
println("Rue de ${user1.name}: $streetName1") // Affiche: Rue de Alice: Main St
println("Rue de ${user2.name}: $streetName2") // Affiche: Rue de Bob: null
println("Rue de ${user3.name}: $streetName3") // Affiche: Rue de Charlie: null
println("Rue de User4: $streetName4") // Affiche: Rue de User4: null
}Cette capacité à chaîner les `?.` rend le code de navigation dans des structures d'objets potentiellement incomplètes extrêmement concis et sûr, comparé aux multiples `if` imbriqués nécessaires dans d'autres langages.
Récapitulatif : `?.` pour la sécurité et la concision
L'opérateur d'appel sécurisé `?.` est l'outil principal pour interagir avec des variables nullables en Kotlin.
- Il permet d'accéder à un membre (propriété ou méthode) d'une référence nullable : `reference?.membre`.
- Si `reference` est `null`, l'expression entière vaut `null` et le membre n'est pas accédé (évite les NPE).
- Si `reference` n'est pas `null`, l'accès au membre se fait normalement.
- Le type de retour de `reference?.membre` est toujours nullable.
- Les appels sécurisés peuvent être chaînés (`a?.b?.c`) pour naviguer en toute sécurité dans des structures d'objets.
Utiliser `?.` est idiomatique en Kotlin et contribue grandement à écrire du code plus sûr et plus lisible lorsqu'on manipule des valeurs potentiellement absentes.