Cet atelier de programmation fait partie du cours Kotlin Bootcamp for Programmers. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. En fonction de vos connaissances, vous pourrez peut-être survoler certaines sections. Ce cours s'adresse aux programmeurs qui connaissent un langage orienté objet et qui souhaitent apprendre Kotlin.
Introduction
Dans cet atelier de programmation, vous allez découvrir plusieurs fonctionnalités utiles en Kotlin, y compris les paires, les collections et les fonctions d'extension.
Plutôt que de créer une seule application exemple, les leçons de ce cours sont conçues pour développer vos connaissances, mais sont semi-indépendantes les unes des autres afin que vous puissiez parcourir les sections que vous connaissez. Pour les relier, de nombreux exemples utilisent un thème d'aquarium. Si vous souhaitez en savoir plus sur l'histoire de l'aquarium, consultez le cours Udacity Kotlin Bootcamp for Programmers.
Ce que vous devez déjà savoir
- Syntaxe des fonctions, classes et méthodes Kotlin
- Utiliser le REPL (Read-Eval-Print Loop) de Kotlin dans IntelliJ IDEA
- Créer une classe dans IntelliJ IDEA et exécuter un programme
Points abordés
- Utiliser des paires et des triplets
- En savoir plus sur les collections
- Définir et utiliser des constantes
- Écrire des fonctions d'extension
Objectifs de l'atelier
- Découvrir les paires, les triplets et les tables de hachage dans le REPL
- Découvrez différentes façons d'organiser les constantes.
- Écrire une fonction d'extension et une propriété d'extension
Dans cette tâche, vous allez découvrir les paires et les triplets, et comment les déstructurer. Les paires et les triplets sont des classes de données prédéfinies pour deux ou trois éléments génériques. Cela peut, par exemple, être utile pour qu'une fonction renvoie plusieurs valeurs.
Supposons que vous ayez un List
de poissons et une fonction isFreshWater()
pour vérifier si le poisson est un poisson d'eau douce ou d'eau salée. List.partition()
renvoie deux listes : l'une avec les éléments pour lesquels la condition est true
et l'autre pour les éléments pour lesquels la condition est false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Étape 1 : Faites des paires et des triples
- Ouvrez le REPL (Tools > Kotlin > Kotlin REPL).
- Créez une paire associant un équipement à son utilisation, puis imprimez les valeurs. Vous pouvez créer une paire en créant une expression qui relie deux valeurs, telles que deux chaînes, avec le mot clé
to
, puis en utilisant.first
ou.second
pour faire référence à chaque valeur.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Créez un triplet et imprimez-le avec
toString()
, puis convertissez-le en liste avectoList()
. Vous créez un triplet à l'aide deTriple()
avec trois valeurs. Utilisez.first
,.second
et.third
pour faire référence à chaque valeur.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
Les exemples ci-dessus utilisent le même type pour toutes les parties de la paire ou du triplet, mais ce n'est pas obligatoire. Les parties peuvent être une chaîne, un nombre ou une liste, par exemple, voire une autre paire ou un autre triplet.
- Créez une paire dont la première partie est elle-même une paire.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
Étape 2 : Déstructurer certaines paires et certains triplets
La déstructuration consiste à séparer les paires et les triples en leurs éléments constitutifs. Attribuez la paire ou le triple au nombre approprié de variables, et Kotlin attribuera la valeur de chaque partie dans l'ordre.
- Déstructurez une paire et imprimez les valeurs.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- Déstructurez un triple et affichez les valeurs.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
Notez que la déstructuration des paires et des triplets fonctionne de la même manière qu'avec les classes de données, comme indiqué dans un précédent atelier de programmation.
Dans cette tâche, vous en apprendrez davantage sur les collections, y compris les listes, et sur un nouveau type de collection, les tables de hachage.
Étape 1 : En savoir plus sur les listes
- Les listes et les listes modifiables ont été présentées dans une leçon précédente. Les listes sont une structure de données très utile. Kotlin fournit donc un certain nombre de fonctions intégrées pour les listes. Consultez cette liste partielle de fonctions pour les listes. Vous trouverez des listes complètes dans la documentation Kotlin pour
List
etMutableList
.
Fonction | Purpose |
| Ajoutez un élément à la liste modifiable. |
| Supprimez un élément d'une liste modifiable. |
| Renvoie une copie de la liste avec les éléments dans l'ordre inverse. |
| Renvoie |
| Renvoie une partie de la liste, du premier index jusqu'au deuxième index (non inclus). |
- Toujours dans le REPL, créez une liste de nombres et appelez
sum()
dessus. Cela résume tous les éléments.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Créez une liste de chaînes et additionnez-la.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- Si l'élément n'est pas quelque chose que
List
sait comment additionner directement, comme une chaîne, vous pouvez spécifier comment l'additionner à l'aide de.sumBy()
avec une fonction lambda, par exemple, pour additionner par la longueur de chaque chaîne. Le nom par défaut d'un argument lambda estit
. Ici,it
fait référence à chaque élément de la liste lors de son parcours.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Vous pouvez faire beaucoup plus avec les listes. Pour voir les fonctionnalités disponibles, vous pouvez créer une liste dans IntelliJ IDEA, ajouter le point, puis consulter la liste d'autocomplétion dans l'info-bulle. Cela fonctionne pour n'importe quel objet. Essayez avec une liste.
- Choisissez
listIterator()
dans la liste, puis parcourez la liste avec une instructionfor
et affichez tous les éléments séparés par des espaces.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Étape 2 : Essayez les tables de hachage
En Kotlin, vous pouvez mapper à peu près n'importe quoi à n'importe quoi d'autre à l'aide de hashMapOf()
. Les tables de hachage sont un peu comme une liste de paires, où la première valeur sert de clé.
- Créez un tableau associatif qui fait correspondre les symptômes (clés) aux maladies des poissons (valeurs).
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Vous pouvez ensuite récupérer la valeur de la maladie en fonction de la clé du symptôme, en utilisant
get()
ou, plus simplement, des crochets[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Essayez de spécifier un symptôme qui ne figure pas sur la carte.
println(cures["scale loss"])
⇒ null
Si une clé n'est pas dans la carte, la tentative de renvoi de la maladie correspondante renvoie null
. En fonction des données cartographiques, il est possible qu'aucune clé ne corresponde. Pour ce type de situations, Kotlin fournit la fonction getOrDefault()
.
- Essayez de rechercher une clé qui ne correspond à rien, en utilisant
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Si vous devez faire plus que renvoyer une valeur, Kotlin fournit la fonction getOrElse()
.
- Modifiez votre code pour utiliser
getOrElse()
au lieu degetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
Au lieu de renvoyer une simple valeur par défaut, le code entre accolades {}
est exécuté. Dans l'exemple, else
renvoie simplement une chaîne, mais il pourrait s'agir d'une fonction plus sophistiquée, comme la recherche d'une page Web contenant un remède et son renvoi.
Tout comme pour mutableListOf
, vous pouvez également créer un mutableMapOf
. Une carte mutable vous permet d'ajouter et de supprimer des éléments. "Mutable" signifie "modifiable", tandis qu'"immuable" signifie "non modifiable".
- Créez une carte d'inventaire modifiable, en mappant une chaîne d'équipement au nombre d'articles. Créez-le avec une épuisette, puis ajoutez trois éponges de nettoyage dans l'inventaire avec
put()
et retirez l'épuisette avecremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
Dans cette tâche, vous allez découvrir les constantes en Kotlin et différentes façons de les organiser.
Étape 1 : Découvrez la différence entre "const" et "val"
- Dans le REPL, essayez de créer une constante numérique. En Kotlin, vous pouvez créer des constantes de premier niveau et leur attribuer une valeur au moment de la compilation à l'aide de
const val
.
const val rocks = 3
La valeur est attribuée et ne peut pas être modifiée, ce qui ressemble beaucoup à la déclaration d'un val
normal. Quelle est donc la différence entre const val
et val
? La valeur de const val
est déterminée au moment de la compilation, tandis que celle de val
est déterminée lors de l'exécution du programme. Cela signifie que val
peut être attribuée par une fonction au moment de l'exécution.
Cela signifie que val
peut se voir attribuer une valeur à partir d'une fonction, mais pas const val
.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
De plus, const val
ne fonctionne qu'au niveau supérieur et dans les classes singleton déclarées avec object
, et non avec les classes standards. Vous pouvez l'utiliser pour créer un fichier ou un objet singleton qui ne contient que des constantes, et les importer selon vos besoins.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Étape 2 : Créez un objet associé
Kotlin ne comporte pas de constantes au niveau de la classe.
Pour définir des constantes dans une classe, vous devez les encapsuler dans des objets compagnons déclarés avec le mot clé companion
. L'objet compagnon est essentiellement un objet singleton dans la classe.
- Créez une classe avec un objet compagnon contenant une constante de chaîne.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
La différence fondamentale entre les objets compagnons et les objets standards est la suivante :
- Les objets compagnons sont initialisés à partir du constructeur statique de la classe conteneur, c'est-à-dire qu'ils sont créés lorsque l'objet est créé.
- Les objets standards sont initialisés de manière différée lors du premier accès à cet objet, c'est-à-dire lors de leur première utilisation.
Il y a plus à savoir, mais pour l'instant, il vous suffit de savoir que vous devez encapsuler les constantes dans des classes dans un objet compagnon.
Dans cette tâche, vous allez apprendre à étendre le comportement des classes. Il est très courant d'écrire des fonctions utilitaires pour étendre le comportement d'une classe. Kotlin fournit une syntaxe pratique pour déclarer ces fonctions utilitaires : les fonctions d'extension.
Les fonctions d'extension vous permettent d'ajouter des fonctions à une classe existante sans avoir à accéder à son code source. Par exemple, vous pouvez les déclarer dans un fichier Extensions.kt qui fait partie de votre package. Cela ne modifie pas la classe, mais vous permet d'utiliser la notation par points pour appeler la fonction sur les objets de cette classe.
Étape 1 : Écrire une fonction d'extension
- Toujours dans le REPL, écrivez une fonction d'extension simple,
hasSpaces()
, pour vérifier si une chaîne contient des espaces. Le nom de la fonction est précédé de la classe sur laquelle elle agit. Dans la fonction,this
fait référence à l'objet sur lequel il est appelé, etit
fait référence à l'itérateur dans l'appelfind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- Vous pouvez simplifier la fonction
hasSpaces()
. Lethis
n'est pas explicitement nécessaire. La fonction peut être réduite à une seule expression et renvoyée. Les accolades{}
qui l'entourent ne sont donc pas non plus nécessaires.
fun String.hasSpaces() = find { it == ' ' } != null
Étape 2 : Découvrez les limites des extensions
Les fonctions d'extension n'ont accès qu'à l'API publique de la classe qu'elles étendent. Les variables private
ne sont pas accessibles.
- Essayez d'ajouter des fonctions d'extension à une propriété marquée
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Examinez le code ci-dessous et déterminez ce qu'il imprimera.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
tirages GreenLeafyPlant
. Vous vous attendez peut-être à ce que aquariumPlant.print()
affiche également GreenLeafyPlant
, car la valeur de plant
lui a été attribuée. Toutefois, le type est résolu au moment de la compilation, donc AquariumPlant
est imprimé.
Étape 3 : Ajouter une propriété d'extension
En plus des fonctions d'extension, Kotlin vous permet également d'ajouter des propriétés d'extension. Comme pour les fonctions d'extension, vous spécifiez la classe que vous étendez, suivie d'un point, puis du nom de la propriété.
- Toujours dans le REPL, ajoutez une propriété d'extension
isGreen
àAquariumPlant
, qui esttrue
si la couleur est verte.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
La propriété isGreen
est accessible comme une propriété normale. Lorsqu'elle est consultée, le getter de isGreen
est appelé pour obtenir la valeur.
- Affichez la propriété
isGreen
de la variableaquariumPlant
et observez le résultat.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Étape 4 : En savoir plus sur les récepteurs pouvant être nuls
La classe que vous étendez est appelée récepteur, et il est possible de rendre cette classe nullable. Dans ce cas, la variable this
utilisée dans le corps peut être null
. Assurez-vous donc de la tester. Vous devez accepter un récepteur nullable si vous vous attendez à ce que les appelants souhaitent appeler votre méthode d'extension sur des variables nullables ou si vous souhaitez fournir un comportement par défaut lorsque votre fonction est appliquée à null
.
- Toujours dans le REPL, définissez une méthode
pull()
qui accepte un récepteur pouvant être nul. Cela est indiqué par un point d'interrogation?
après le type, avant le point. Dans le corps, vous pouvez tester sithis
n'est pasnull
en utilisant questionmark-dot-apply?.apply.
.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- Dans ce cas, aucune sortie n'est générée lorsque vous exécutez le programme. Étant donné que
plant
estnull
, leprintln()
interne n'est pas appelé.
Les fonctions d'extension sont très puissantes, et la majeure partie de la bibliothèque standard Kotlin est implémentée en tant que fonctions d'extension.
Dans cette leçon, vous avez découvert les collections et les constantes, et vous avez pu constater la puissance des fonctions et des propriétés d'extension.
- Les paires et les triplets peuvent être utilisés pour renvoyer plusieurs valeurs à partir d'une fonction. Exemples :
val twoLists = fish.partition { isFreshWater(it) }
- Kotlin propose de nombreuses fonctions utiles pour
List
, telles quereversed()
,contains()
etsubList()
. - Un
HashMap
peut être utilisé pour mapper des clés à des valeurs. Par exemple :val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Déclarez les constantes au moment de la compilation à l'aide du mot clé
const
. Vous pouvez les placer au niveau supérieur, les organiser dans un objet singleton ou les placer dans un objet compagnon. - Un objet compagnon est un objet singleton dans une définition de classe, défini avec le mot clé
companion
. - Les fonctions et propriétés d'extension peuvent ajouter des fonctionnalités à une classe. Exemples :
fun String.hasSpaces() = find { it == ' ' } != null
- Un récepteur nullable vous permet de créer des extensions sur une classe qui peut être
null
. L'opérateur?.
peut être associé àapply
pour vérifier la présence denull
avant d'exécuter le code. Par exemple :this?.apply { println("removing $this") }
Documentation Kotlin
Si vous souhaitez en savoir plus sur un sujet abordé dans ce cours ou si vous êtes bloqué, https://kotlinlang.org est le meilleur point de départ.
Tutoriels Kotlin
Le site Web https://try.kotlinlang.org inclut des tutoriels complets appelés Kotlin Koans, un interpréteur Web et un ensemble complet de documentation de référence avec des exemples.
Cours Udacity
Pour consulter le cours Udacity sur ce sujet, reportez-vous à la formation Kotlin pour les programmeurs.
IntelliJ IDEA
La documentation d'IntelliJ IDEA est disponible sur le site Web de JetBrains.
Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :
- Attribuer des devoirs si nécessaire
- Indiquer aux élèves comment rendre leurs devoirs
- Noter les devoirs
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.
Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondre aux questions suivantes
Question 1
Laquelle des propositions suivantes renvoie une copie d'une liste ?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Question 2
Quelle fonction d'extension sur class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
générera une erreur de compilation ?
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
Question 3
Parmi les propositions suivantes, laquelle n'est pas un endroit où vous pouvez définir des constantes avec const val
?
▢ au premier niveau d'un fichier
▢ dans les cours réguliers
▢ dans les objets singleton
▢ dans les objets associés
Passez à la leçon suivante :
Pour obtenir un aperçu du cours, y compris des liens vers d'autres ateliers de programmation, consultez Formation Kotlin pour les programmeurs : bienvenue dans le cours.