Cet atelier de programmation fait partie du cours Kotlin Bootcamp for Programmers (Formation Kotlin pour les programmeurs). Vous tirerez le meilleur parti de ce cours si vous suivez les ateliers de programmation dans l'ordre. En fonction de vos connaissances, vous pouvez parcourir certaines sections. Ce cours s'adresse aux programmeurs qui connaissent un langage orienté objet et souhaitent apprendre l'Kotlin.
Introduction
Dans cet atelier de programmation, vous avez découvert différentes fonctionnalités utiles en langage Kotlin, comme les paires, les collections et les fonctions d'extension.
Plutôt que de créer un exemple d'application, les leçons de ce cours sont conçues pour développer vos connaissances, mais elles ne doivent pas être indépendantes les unes des autres pour vous permettre de parcourir les sections que vous connaissez déjà. Pour les assembler, de nombreux exemples utilisent un thème d'aquarium. Et si vous voulez voir l'histoire complète 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 la boucle REPL (Read-Eval-Print Loop) dans IntelliJ IDEA
- Créer une classe dans IntelliJ IDEA et exécuter un programme
Points abordés
- Travailler avec des paires et des triples
- En savoir plus sur les collections
- Définir et utiliser des constantes
- Écriture des fonctions d'extension
Objectifs de l'atelier
- En savoir plus sur les paires, les triples et le hachage des cartes dans le REPL
- Découvrir différentes façons d'organiser les constantes
- Écrire une fonction et une propriété d'extension
Dans cette tâche, vous allez découvrir les paires et les triples, puis les déstructurer. Les paires et les triples sont des classes de données prédéfinies pour deux ou trois éléments génériques. Cela peut, par exemple, s'avérer utile si une fonction renvoie plusieurs valeurs.
Supposons que vous ayez une List
de poisson et une fonction isFreshWater()
pour vérifier si le poisson est d'eau douce ou d'eau salée. List.partition()
renvoie deux listes, l'une avec les éléments dont l'état est true
, et l'autre pour les éléments dont l'état est false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Étape 1: Organiser des paires et des triples
- Ouvrez le certificat REPL (Tools >, Kotlin >, Kotlin REPL)
- Créez une paire, en associant un équipement à son utilisation, puis imprimez les valeurs. Vous pouvez créer une paire en créant une expression reliant 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 triple et imprimez-le avec
toString()
, puis convertissez-le en liste avectoList()
. Vous créez un triple en utilisantTriple()
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 triple, mais ce n'est pas obligatoire. Il peut s'agir, par exemple, d'une chaîne, d'un nombre ou d'une liste, par exemple une autre paire ou un triple.
- Créez une paire dans laquelle 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 triples
La séparation des paires et des triples est appelée déstructuration. Attribuez la paire ou le triple au nombre de variables approprié, 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 imprimez 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 triples fonctionne de la même manière que pour les classes de données, qui ont été abordées dans un précédent atelier de programmation.
Dans cette tâche, vous allez en apprendre davantage sur les collections, y compris sur les listes, et sur le nouveau type de collection et les hachages de cartes.
Étape 1: Apprenez-en plus sur les listes
- Les listes et les listes modifiables ont été introduites dans une leçon précédente. Étant donné qu'il s'agit d'une structure de données très utile, Kotlin fournit un certain nombre de fonctions intégrées pour les listes. Consultez cette liste partielle de fonctions pour les listes. Vous trouverez des fiches complètes dans la documentation Kotlin pour
List
etMutableList
.
Fonction | Usage |
| Ajoutez un élément à la liste modifiable. |
| Supprimez un élément d'une liste modifiable. |
| Renvoyez une copie de la liste avec les éléments dans l'ordre inverse. |
| Renvoyez |
| Renvoyez une partie de la liste entre le premier et le deuxième. |
- Toujours en train de travailler dans le REPL, créez une liste de numéros et appelez
sum()
. Nous additionnons tous les éléments.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Créez une liste de chaînes et ajoutez-y la liste.
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 un élément
List
qui sait comment effectuer directement une somme (une chaîne, par exemple), vous pouvez indiquer comment la somme à l'aide de.sumBy()
avec une fonction lambda, par exemple, pour additionner la longueur de chaque chaîne. Par défaut, le nom d'un argument lambda est défini surit
. Ici,it
fait référence à chaque élément de la liste lorsque celle-ci est traversée.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Vous pouvez faire bien plus avec les listes. Pour voir la fonctionnalité disponible, vous pouvez créer une liste dans IntelliJ IDEA, ajouter un point, puis observer la liste de saisie semi-automatique dans l'info-bulle. Cela fonctionne pour tous les objets. Faites un essai avec une liste.
- Choisissez
listIterator()
dans la liste, puis parcourez la liste avec une instructionfor
et imprimez 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: Essayer les cartes de hachage
En langage Kotlin, vous pouvez mapper pratiquement tout ce que vous voulez avec hashMapOf()
. Les mappages de hachage fonctionnent comme une liste de paires, où la première valeur agit comme une clé.
- Créez une carte de hachage qui met en correspondance les symptômes, les clés et les maladies des poissons.
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é des symptômes, en utilisant
get()
ou entre crochets[]
plus courts.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Essayez de spécifier un symptôme non visible sur la carte.
println(cures["scale loss"])
⇒ null
Si une clé ne figure pas dans la carte, une tentative de renvoyer la maladie correspondante renvoie null
. En fonction des données de la carte, il est courant qu'aucune correspondance ne soit établie pour une clé possible. Pour des cas similaires, Kotlin fournit la fonction getOrDefault()
.
- Essayez de rechercher une clé sans correspondance en utilisant
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Si vous souhaitez aller au-delà du simple affichage d'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 valeur par défaut simple, tout code se trouve entre les accolades {}
. Dans l'exemple, else
renvoie simplement une chaîne, mais cela peut être aussi sophistiqué que de rechercher une page Web avec un traitement et de la renvoyer.
Tout comme mutableListOf
, vous pouvez également créer un mutableMapOf
. Une carte modifiable vous permet de placer et de supprimer des éléments. "muable" signifie simplement "changeable", tandis que "immuable" signifie "non modifiable".
- Créez un plan d'inventaire modifiable, qui mappe une chaîne d'équipement au nombre d'éléments. Créez-le avec un filet de poisson, puis ajoutez trois nettoyeurs pour aquariums dans l'inventaire avec
put()
et supprimez le filet de poisson 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 langage Kotlin et différentes manières de les organiser.
Étape 1: Différence entre les constantes et les valeurs
- Dans le protocole REPL, essayez de créer une constante numérique. En langage Kotlin, vous pouvez définir des constantes de niveau supérieur et leur attribuer une valeur au moment de la compilation à l'aide de
const val
.
const val rocks = 3
Cette valeur est attribuée à un utilisateur et ne peut pas être modifiée, ce qui ressemble beaucoup à une déclaration val
standard. Alors, quelle est la différence entre const val
et val
? La valeur de const val
est déterminée au moment de la compilation, tandis que la valeur de val
est déterminée pendant l'exécution du programme. Par conséquent, val
peut être attribué par une fonction au moment de l'exécution.
Cela signifie que val
peut recevoir une valeur pour une fonction, mais pas const val
.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
En outre, const val
ne fonctionne qu'au premier niveau 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 contenant uniquement des constantes, puis les importer si nécessaire.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Étape 2: Créez un objet associé
Kotlin n'a pas de concept de constantes au niveau de la classe.
Pour définir des constantes dans une classe, vous devez les encapsuler dans des objets associés déclarés avec le mot clé companion
. L'objet associé est en fait un objet singleton au sein de la classe.
- Créez une classe avec un objet associé contenant une constante de chaîne.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
La différence de base entre les objets associés et les objets standards est la suivante:
- Les objets associés sont initialisés à partir du constructeur statique de la classe parent. Autrement dit, ils sont créés en même temps que l'objet.
- Les objets standards sont initialisés de manière différée lors du premier accès à cet objet, c'est-à-dire lorsqu'ils sont utilisés pour la première fois.
Il suffit d'encapsuler des constantes dans des classes d'un objet associé.
Dans cette tâche, vous allez découvrir comment é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 d'utilitaire :
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. Cette classe ne modifie en rien la classe, mais elle vous permet d'utiliser la notation à point lors de l'appel de la fonction sur des objets de cette classe.
Étape 1: Rédigez une fonction d'extension
- Toujours en train de travailler 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 il opère. Dans la fonction,this
fait référence à l'objet auquel elle est appelée, etit
désigne l'itérateur lors de l'appel defind()
.
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()
.this
n'est pas explicitement nécessaire et la fonction peut être réduite à une seule expression et renvoyée. Les accolades{}
sont donc inutiles.
fun String.hasSpaces() = find { it == ' ' } != null
Étape 2: Comprendre les limites des extensions
Les fonctions d'extension n'ont accès qu'à l'API publique de la classe qu'elles étendent. Les variables qui sont private
ne sont pas accessibles.
- Essayez d'ajouter des fonctions d'extension à une propriété marquée comme
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 réfléchissez à ce qui sera imprimé.
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()
imprime GreenLeafyPlant
. Vous pouvez vous attendre à ce que aquariumPlant.print()
imprime également les GreenLeafyPlant
, car la valeur de ce champ a été attribuée à plant
. Cependant, ce type est résolu au moment de la compilation, de sorte que AquariumPlant
est imprimé.
Étape 3: Ajouter une propriété d'extension
En plus des fonctions d'extension, Kotlin vous permet 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 en train de travailler dans le REPL, ajoutez une propriété d'extension
isGreen
àAquariumPlant
, qui correspond àtrue
si la couleur est verte.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
La propriété isGreen
est accessible de la même manière qu'une propriété standard. Lorsqu'elle est utilisée, le getter de isGreen
est appelé pour obtenir la valeur.
- Imprimez la propriété
isGreen
pour la variableaquariumPlant
et observez le résultat.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Étape 4: Comprendre les récepteurs pouvant être vides
La classe que vous étendez est appelée destinataire. Il est possible de la rendre nulle. Dans ce cas, la variable this
utilisée dans le corps peut être null
. Assurez-vous donc d'effectuer le test. Il est préférable d'utiliser un récepteur pouvant accepter les valeurs nulles si vous prévoyez que les appelants appelleront votre méthode d'extension sur des variables pouvant être nulles ou si vous voulez fournir un comportement par défaut lorsque votre fonction est appliquée à null
.
- Toujours en train de travailler dans le REPL, définissez une méthode
pull()
qui accepte un récepteur pouvant être vide. Dans ce cas, un point d'interrogation?
apparaît après le type, avant le point. Dans le corps, vous pouvez vérifier quethis
n'est pasnull
en utilisant le point d'interrogation-application?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- Dans ce cas, il n'y a aucun résultat lorsque vous exécutez le programme. Comme
plant
estnull
, leprintln()
interne n'est pas appelé.
Les fonctions d'extension sont très performantes, et la plupart des bibliothèques standards Kotlin sont implémentées en tant que fonctions d'extension.
Dans cette leçon, vous avez découvert les collections, découvert les constantes et découvert la puissance des fonctions et des propriétés d'extension.
- Les paires et les triples peuvent être utilisées 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()
. HashMap
permet de mapper des clés avec des valeurs. Exemple :val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Déclarez les constantes de 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 associé. - Un objet associé 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 pouvant être vide vous permet de créer des extensions sur une classe qui peut être
null
. L'opérateur?.
peut être associé àapply
pour vérifiernull
avant d'exécuter du code. Exemple:this?.apply { println("removing $this") }
Documentation Kotlin
Si vous souhaitez en savoir plus sur un sujet de ce cours ou si vous rencontrez des difficultés, https://kotlinlang.org constitue le meilleur point de départ.
Tutoriels Kotlin
Le site Web https://try.kotlinlang.org propose des tutoriels enrichis appelés Kotlin Koans, un interpréteur Web ainsi qu'une documentation de référence complète avec des exemples.
Cours Udacity
Pour afficher le cours Udacity sur ce sujet, consultez Kotlin Bootcamp for Programmers (Formation Kotlin pour les programmeurs).
IntelliJ IDEA
Consultez la documentation sur IntelliJ IDEA sur le site Web de JetBrains.
Cette section répertorie les devoirs possibles pour les élèves qui effectuent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. C'est à l'enseignant de procéder comme suit:
- Si nécessaire, rendez-les.
- Communiquez aux élèves comment rendre leurs devoirs.
- Notez les devoirs.
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ils n'ont pas besoin d'attribuer les devoirs de leur choix.
Si vous suivez vous-même cet atelier de programmation, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondez à ces questions.
Question 1
Laquelle de ces propositions renvoie une copie d'une liste ?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Question 2
Parmi les fonctions d'extension de class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
suivantes, laquelle génère 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 éléments suivants, lequel ne permet pas de définir des constantes avec const val
?
▢ en haut d'un fichier
▢ dans les classes standards
▢ dans les objets singleton
▢ dans les objets associés
Passez à la leçon suivante:
Pour une présentation du cours, y compris des liens vers d'autres ateliers de programmation, consultez le "Kotlin Bootcamp for Programmers: Welcome to the course."