Utiliser des listes en Kotlin

1. Avant de commencer

Il est courant de créer des listes pour toutes sortes de situations de la vie quotidienne : liste des tâches, liste d'invités à un événement, liste d'idées cadeaux, liste de courses, etc. En programmation aussi, les listes s'avèrent très utiles. Par exemple, une application peut contenir une liste d'articles de presse, de titres musicaux, d'événements d'agenda ou de posts sur les réseaux sociaux.

Apprendre à créer et à utiliser des listes est un concept de programmation important que vous pouvez ajouter à votre panoplie. Cela vous permettra de créer des applications plus sophistiquées.

Dans cet atelier de programmation, vous allez utiliser Kotlin Playground pour vous familiariser avec les listes en Kotlin et créer un programme permettant de commander différentes variétés de soupes de nouilles. Je parie que vous en avez déjà l'eau à la bouche.

Conditions préalables

  • Vous maîtrisez Kotlin Playground pour créer et modifier des programmes Kotlin.
  • Vous connaissez les concepts de programmation Kotlin exposés dans le Module 1 du cours "Principes de base d'Android en Kotlin" : fonction main(), arguments des fonctions et valeurs de retour, variables, types de données et opérations, et instructions de flux de contrôle.
  • Vous êtes capable de définir une classe Kotlin, de créer une instance d'objet à partir de celle-ci, et d'accéder à ses propriétés et méthodes.
  • Vous êtes capable de créer des sous-classes et vous comprenez le concept d'héritage.

Points abordés

  • Comment créer et utiliser des listes en Kotlin
  • Différence entre List et MutableList, et quand les utiliser
  • Comment effectuer une itération sur tous les éléments d'une liste et effectuer une action sur chacun d'eux

Objectifs de l'atelier

  • Vous allez tester des listes et des opérations de liste dans Kotlin Playground.
  • Vous allez créer un programme de commande de repas qui utilise des listes dans Kotlin Playground.
  • Ce programme vous permet de créer une commande, d'y ajouter des nouilles et des légumes, puis de calculer le coût total de la commande.

Ce dont vous avez besoin

2. Présentation des listes

Dans les ateliers de programmation précédents, vous avez étudié les types de données de base en Kotlin, tels que Int, Double, Boolean et String. Ils vous permettent de stocker un certain type de valeur dans une variable. Mais que se passe-t-il si vous souhaitez stocker plusieurs valeurs ? C'est là qu'un type de données List se révèle utile.

Une liste est un ensemble d'éléments dans un ordre spécifique. Il existe deux types de listes en Kotlin :

  • Liste en lecture seule : impossible de modifier List après sa création.
  • Liste modifiable : MutableList peut être modifiée après sa création, ce qui signifie que vous pouvez ajouter, supprimer ou mettre à jour ses éléments.

Lorsque vous utilisez List ou MutableList, vous devez spécifier le type d'élément qu'il peut contenir. Par exemple, List<Int> contient une liste d'entiers et List<String> contient une liste de chaînes. Si vous définissez une classe Car dans votre programme, vous pouvez créer une List<Car> contenant une liste d'instances d'objet Car.

Pour bien comprendre ce que sont les listes, rien ne vaut la pratique.

Créer une liste

  1. Ouvrez Kotlin Playground et supprimez le code existant.
  2. Ajoutez une fonction main() vide. Toutes les étapes de code suivantes seront incluses dans cette fonction main().
fun main() {

}
  1. Dans main(), créez une variable appelée numbers de type List<Int>, car elle contiendra une liste d'entiers en lecture seule. Créez une List à l'aide de la fonction de bibliothèque standard Kotlin listOf(), puis transmettez les éléments de la liste en tant qu'arguments séparés par des virgules. listOf(1, 2, 3, 4, 5, 6) renvoie une liste en lecture seule d'entiers compris entre 1 et 6.
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
  1. Si le type de données de la variable peut être déduit (ou inféré) à partir de la valeur figurant à droite de l'opérateur d'affectation (=), vous pouvez omettre cet élément. Vous pouvez donc raccourcir cette ligne de code comme suit :
val numbers = listOf(1, 2, 3, 4, 5, 6)
  1. Utilisez println() pour imprimer la liste numbers.
println("List: $numbers")

Pour rappel, si vous saisissez "$" dans la chaîne, ce qui suit est une expression qui sera évaluée et ajoutée à cette chaîne (voir modèles de chaîne). Cette ligne de code peut également être écrite sous la forme println("List: " + numbers).

  1. Récupérez la taille d'une liste à l'aide de la propriété numbers.size, puis imprimez-la également.
println("Size: ${numbers.size}")
  1. Exécutez votre programme. Vous obtenez comme résultat une liste de tous les éléments de la liste, ainsi que sa taille. Notez les crochets [] qui indiquent qu'il s'agit d'une List. Les éléments de numbers sont placés entre crochets et séparés par des virgules. Notez également que les éléments sont dans le même ordre que lorsque vous les avez créés.
List: [1, 2, 3, 4, 5, 6]
Size: 6

Éléments de la liste d'accès

La spécificité d'une liste est de vous permettre d'accéder à chacun de ses éléments par son index, qui est un nombre entier représentant la position. Voici un diagramme de la liste numbers que nous avons créé. Il affiche chaque élément et l'index correspondant.

cb6924554804458d.png

L'index est en fait un décalage par rapport au premier élément. Par exemple, lorsque vous dites list[2], vous ne demandez pas le deuxième élément de la liste, mais l'élément qui est décalé de deux positions par rapport au premier. Par conséquent, list[0] est le premier élément (décalage de 0), list[1] est le deuxième élément (décalage de 1), list[2] est le troisième élément (décalage de 2), et ainsi de suite.

Ajoutez le code suivant après le code existant dans la fonction main(). Exécutez le code après chaque étape pour vérifier que le résultat correspond à vos attentes.

  1. Imprimez le premier élément de la liste au niveau de l'index 0. Vous pouvez appeler la fonction get() avec l'index souhaité sous la forme numbers.get(0) ou utiliser la syntaxe abrégée avec des crochets autour de l'index sous la forme numbers[0].
println("First element: ${numbers[0]}")
  1. Imprimez ensuite le deuxième élément de la liste au niveau de l'index 1.
println("Second element: ${numbers[1]}")

Les valeurs d'index valides ("indices") d'une liste vont de 0 jusqu'au dernier index, qui correspond à la taille de la liste moins 1. Autrement dit, pour votre liste numbers, les index vont de 0 à 5.

  1. Imprimez le dernier élément de la liste en utilisant numbers.size - 1 pour calculer son index, qui doit être 5. L'accès à l'élément situé au niveau du cinquième index doit renvoyer 6 comme résultat.
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
  1. Kotlin accepte également les opérations first() et last() sur une liste. Essayez d'appeler numbers.first() et numbers.last(), et observez le résultat.
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")

Comme vous pouvez le constater, numbers.first() renvoie le premier élément de la liste et numbers.last() renvoie le dernier élément.

  1. La méthode contains() permet également de déterminer si un élément donné se trouve dans la liste. Par exemple, si vous disposez de la liste des noms d'employés d'une entreprise, vous pouvez utiliser la méthode contains() pour savoir si un nom donné figure dans cette liste.

Dans votre liste numbers, appelez la méthode contains() avec l'un des entiers présents. numbers.contains(4) renvoie la valeur true. Appelez ensuite la méthode contains() avec un entier qui ne figure pas dans votre liste. numbers.contains(7) renvoie false.

println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
  1. Votre code, une fois terminé, doit se présenter comme suit. Les commentaires sont facultatifs.
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6)
    println("List: $numbers")
    println("Size: ${numbers.size}")

    // Access elements of the list
    println("First element: ${numbers[0]}")
    println("Second element: ${numbers[1]}")
    println("Last index: ${numbers.size - 1}")
    println("Last element: ${numbers[numbers.size - 1]}")
    println("First: ${numbers.first()}")
    println("Last: ${numbers.last()}")

    // Use the contains() method
    println("Contains 4? ${numbers.contains(4)}")
    println("Contains 7? ${numbers.contains(7)}")
}
  1. Exécutez votre code. Voici la sortie.
List: [1, 2, 3, 4, 5, 6]
Size: 6
First element: 1
Second element: 2
Last index: 5
Last element: 6
First: 1
Last: 6
Contains 4? true
Contains 7? false

Les listes sont en lecture seule

  1. Supprimez le code de Kotlin Playground et remplacez-le par le code suivant. La liste colors est initialisée sur une liste de trois couleurs représentée par Strings.
fun main() {
    val colors = listOf("green", "orange", "blue")
}
  1. Pour rappel, vous ne pouvez ni ajouter ni modifier d'éléments dans une List en lecture seule. Voyez ce qui se passe lorsque vous essayez d'ajouter un élément à la liste ou de modifier un élément de la liste en le définissant sur une nouvelle valeur.
colors.add("purple")
colors[0] = "yellow"
  1. Vous exécutez votre code et plusieurs messages d'erreur sont renvoyés. En substance, les erreurs indiquent que la méthode add() n'existe pas pour List et que vous n'êtes pas autorisé à modifier la valeur d'un élément.

dd21aaccdf3528c6.png

  1. Supprimez le code incorrect.

Vous avez pu constater qu'il n'était pas possible de modifier une liste en lecture seule. Cependant, il existe plusieurs opérations qui n'ont pas d'incidence sur les listes, mais qui renvoient une nouvelle liste. C'est le cas de reversed() et sorted(). La fonction reversed() renvoie une nouvelle liste dans laquelle les éléments sont dans l'ordre inverse. sorted() renvoie une nouvelle liste dans laquelle les éléments sont triés par ordre croissant.

  1. Ajoutez du code pour inverser la liste colors. Imprimez la sortie. Il s'agit d'une nouvelle liste contenant les éléments de colors dans l'ordre inverse.
  2. Ajoutez une deuxième ligne de code pour imprimer la list d'origine. Vous pourrez ainsi constater que la liste d'origine n'a pas changé.
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
  1. Voici la sortie des deux listes imprimées.
Reversed list: [blue, orange, green]
List: [green, orange, blue]
  1. Ajoutez du code pour renvoyer une version classée d'une List à l'aide de la fonction sorted().
println("Sorted list: ${colors.sorted()}")

Le résultat est une nouvelle liste de couleurs classées par ordre alphabétique. Super !

Sorted list: [blue, green, orange]
  1. Vous pouvez également exécuter la fonction sorted() sur une liste de nombres non triés.
val oddNumbers = listOf(5, 3, 7, 1)
println("List: $oddNumbers")
println("Sorted list: ${oddNumbers.sorted()}")
List: [5, 3, 7, 1]
Sorted list: [1, 3, 5, 7]

Vous pouvez maintenant constater à quel point il est utile de pouvoir créer des listes. Toutefois, il serait intéressant de pouvoir modifier la liste après sa création. Nous allons donc nous pencher maintenant sur les listes modifiables.

3. Présentation des listes modifiables

Les listes de ce type peuvent être modifiées après leur création. Vous pouvez ajouter, supprimer ou modifier des éléments. Vous pouvez également effectuer toutes les opérations réalisables sur les listes en lecture seule. Les listes modifiables sont de type MutableList. Vous pouvez les créer en appelant mutableListOf().

Créer une MutableList

  1. Supprimez le code existant dans main().
  2. Dans la fonction main(), créez une liste modifiable vide et attribuez-la à une variable val appelée entrees.
val entrees = mutableListOf()

L'erreur suivante se produit si vous essayez d'exécuter votre code.

Not enough information to infer type variable T

Comme indiqué précédemment, lorsque vous créez une MutableList ou une List, Kotlin tente de déduire le type des éléments contenus dans la liste à partir des arguments transmis. Par exemple, si vous écrivez listOf("noodles"), Kotlin en déduit que vous souhaitez créer une liste de String. Lorsque vous initialisez une liste vide (c'est-à-dire sans éléments), Kotlin ne peut pas déduire le type des éléments. Vous devez donc l'indiquer explicitement. Pour ce faire, ajoutez le type entre chevrons, immédiatement après mutableListOf ou listOf. Dans la documentation, cela peut être représenté sous la forme <T>, où T correspond au paramètre de type.

  1. Corrigez la déclaration de variable pour indiquer que vous souhaitez créer une liste modifiable de type String.
val entrees = mutableListOf<String>()

Vous pouvez également corriger l'erreur en spécifiant, à l'avance, le type de données de la variable.

val entrees: MutableList<String> = mutableListOf()
  1. Imprimez la liste.
println("Entrees: $entrees")
  1. La sortie affiche [] pour une liste vide.
Entrees: []

Ajouter des éléments à une liste

L'utilisation de listes modifiables devient intéressante lorsque vous ajoutez, supprimez et mettez à jour des éléments.

  1. Ajoutez "noodles" à la liste avec entrees.add("noodles").. La fonction add() renvoie true si l'élément a bien été ajouté à la liste. Sinon, false est renvoyé.
  2. Imprimez la liste pour vérifier que "noodles" a bien été ajouté.
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")

Voici la sortie :

Add noodles: true
Entrees: [noodles]
  1. Ajoutez un autre élément "spaghetti" à la liste.
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")

La liste entrees obtenue contient maintenant deux éléments.

Add spaghetti: true
Entrees: [noodles, spaghetti]

Au lieu d'ajouter des éléments un par un à l'aide de add(), vous pouvez en ajouter plusieurs à la fois à l'aide de addAll() et transmettre une liste.

  1. Créez une liste de moreItems. Comme aucune modification ne sera nécessaire, définissez-la comme val et faites-en sorte qu'elle ne soit pas modifiable.
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
  1. Utilisez addAll() pour ajouter tous les éléments de la nouvelle liste à entrees. Imprimez la liste obtenue.
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")

La sortie indique que la liste a bien été ajoutée. La liste entrees contient désormais un total de cinq éléments.

Add list: true
Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
  1. Essayez maintenant d'ajouter un nombre à cette liste.
entrees.add(10)

Cette opération échoue et une erreur est renvoyée :

The integer literal does not conform to the expected type String

Cela est dû au fait que vous essayez d'ajouter un Int alors que la liste entrees attend des éléments de type String. N'oubliez pas d'ajouter uniquement des éléments dont le type de données est adapté à une liste. Sinon, une erreur de compilation sera renvoyée. C'est l'une des méthodes utilisées par Kotlin pour garantir la sûreté de typage de votre code.

  1. Supprimez la ligne de code incorrecte afin que votre code soit compilé.

Supprimer des éléments d'une liste

  1. Appelez remove() pour supprimer "spaghetti" de la liste. Imprimez de nouveau la liste.
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
  1. La suppression de "spaghetti" renvoie la valeur "true", car l'élément était présent dans la liste et a pu être supprimé. Il ne reste plus que quatre éléments dans la liste.
Remove spaghetti: true
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. Que se passe-t-il si vous essayez de supprimer un élément qui ne figure pas dans la liste ? Essayez de supprimer "rice" de la liste avec entrees.remove("rice").
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")

La méthode remove() renvoie false, car l'élément n'existe pas et ne peut donc pas être supprimé. La liste reste inchangée et ne contient toujours que quatre éléments. Sortie :

Remove item that doesn't exist: false
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. Vous pouvez également spécifier l'index de l'élément à supprimer. Utilisez removeAt() pour supprimer l'élément au niveau de l'index 0.
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")

La valeur renvoyée pour removeAt(0) est le premier élément ("noodles") qui a été supprimé de la liste. Il reste à présent trois éléments dans la liste entrees.

Remove first element: noodles
Entrees: [ravioli, lasagna, fettuccine]
  1. Si vous souhaitez effacer toute la liste, vous pouvez appeler clear().
entrees.clear()
println("Entrees: $entrees")

La sortie affiche maintenant une liste vide.

Entrees: []
  1. Kotlin vous permet de vérifier si une liste est vide à l'aide de la fonction isEmpty(). Essayez d'imprimer entrees.isEmpty().
println("Empty? ${entrees.isEmpty()}")

La valeur "true" doit être renvoyée, car la liste est actuellement vide.

Empty? true

La méthode isEmpty() est utile si vous souhaitez effectuer une opération sur une liste ou accéder à un élément donné, tout en vous assurant d'abord que la liste n'est pas vide.

Voici le code que vous avez écrit pour les listes modifiables. Les commentaires sont facultatifs.

fun main() {
    val entrees = mutableListOf<String>()
    println("Entrees: $entrees")

    // Add individual items using add()
    println("Add noodles: ${entrees.add("noodles")}")
    println("Entrees: $entrees")
    println("Add spaghetti: ${entrees.add("spaghetti")}")
    println("Entrees: $entrees")

    // Add a list of items using addAll()
    val moreItems = listOf("ravioli", "lasagna", "fettuccine")
    println("Add list: ${entrees.addAll(moreItems)}")
    println("Entrees: $entrees")

    // Remove an item using remove()
    println("Remove spaghetti: ${entrees.remove("spaghetti")}")
    println("Entrees: $entrees")
    println("Remove item that doesn't exist: ${entrees.remove("rice")}")
    println("Entrees: $entrees")

    // Remove an item using removeAt() with an index
    println("Remove first element: ${entrees.removeAt(0)}")
    println("Entrees: $entrees")

    // Clear out the list
    entrees.clear()
    println("Entrees: $entrees")

    // Check if the list is empty
    println("Empty? ${entrees.isEmpty()}")
}

4. Lire une liste en boucle

Pour effectuer une opération sur chaque élément d'une liste, vous pouvez la lire en boucle (on parle également d'itération de liste). Vous pouvez utiliser les boucles avec Lists et MutableLists.

Boucles While

while est un type de boucle. Une boucle while commence par le mot clé while en langage Kotlin. Elle contient un bloc de code (entre accolades) qui est exécuté de façon répétée, à condition que l'expression entre parenthèses soit vraie. Pour éviter que le code s'exécute indéfiniment (c'est ce que l'on appelle une boucle infinie), le bloc de code doit contenir une logique qui modifie la valeur de l'expression. Ainsi, au final, cette expression sera fausse et vous cesserez d'exécuter la boucle. À ce stade, vous quittez la boucle while et vous continuez d'exécuter le code qui la suit.

while (expression) {
    // While the expression is true, execute this code block
}

Utilisez une boucle while pour itérer une liste. Créez une variable pour effectuer le suivi de l'index observé actuellement dans la liste. La variable index continue d'augmenter de 1 chaque fois que vous atteignez le dernier index de la liste, après quoi vous quittez la boucle.

  1. Supprimez le code existant dans Kotlin Playground et utilisez une fonction main() vide.
  2. Supposons que vous organisiez une fête. Créez une liste où chaque élément représente le nombre de personnes ayant répondu pour chaque famille. La première famille a indiqué que deux personnes assisteraient à la fête. La deuxième famille a indiqué que quatre personnes seraient présentes, et ainsi de suite.
val guestsPerFamily = listOf(2, 4, 1, 3)
  1. Déterminez le nombre total d'invités. Écrivez une boucle pour obtenir la réponse. Créez un var pour le nombre total d'invités et initialisez-le sur 0.
var totalGuests = 0
  1. Initialisez une var pour la variable index, comme décrit précédemment.
var index = 0
  1. Écrivez une boucle while pour itérer la liste. La condition est de continuer à exécuter le bloc de code, à condition que la valeur index soit inférieure à la taille de la liste.
while (index < guestsPerFamily.size) {

}
  1. Dans la boucle, récupérez l'élément de la liste au niveau de l'index actuel et ajoutez-le à la variable du nombre total d'invités. N'oubliez pas que totalGuests += guestsPerFamily[index] est identique à totalGuests = totalGuests + guestsPerFamily[index].

Comme vous pouvez le voir, la dernière ligne de la boucle incrémente la variable index de 1 à l'aide de index++, de sorte que l'itération suivante de la boucle recherche la famille suivante dans la liste.

while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
  1. Après la boucle while, vous pouvez imprimer le résultat.
while ... {
    ...
}
println("Total Guest Count: $totalGuests")
  1. Exécutez le programme. Vous obtenez la sortie suivante. Vous pouvez vérifier qu'il s'agit de la bonne réponse en ajoutant manuellement les chiffres dans la liste.
Total Guest Count: 10

Voici l'extrait de code complet :

val guestsPerFamily = listOf(2, 4, 1, 3)
var totalGuests = 0
var index = 0
while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
println("Total Guest Count: $totalGuests")

Avec une boucle while, vous deviez écrire du code pour créer une variable permettant d'effectuer le suivi de l'index, obtenir l'élément au niveau de l'index dans la liste et mettre à jour cette variable d'index. Il existe une méthode encore plus rapide et concise pour itérer une liste : utiliser des boucles for.

Boucles For

for est un autre type de boucle qui simplifie considérablement la lecture d'une liste en boucle. Cette boucle commence par le mot clé for en Kotlin, suivi du bloc de code entre accolades. La condition d'exécution du bloc de code est indiquée entre parenthèses.

for (number in numberList) {
   // For each element in the list, execute this code block
}

Dans cet exemple, la variable number est définie sur la valeur du premier élément de numberList, et le bloc de code est exécuté. Ensuite, la variable number est automatiquement mise à jour pour devenir l'élément suivant de numberList et le bloc de code est de nouveau exécuté. Cette opération se répète pour chaque élément de la liste, jusqu'à atteindre la fin de numberList.

  1. Supprimez le code existant dans Kotlin Playground et remplacez-le par le code suivant :
fun main() {
    val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
  1. Ajoutez une boucle for pour imprimer tous les éléments de la liste names.
for (name in names) {
    println(name)
}

C'est beaucoup plus simple que de devoir écrire cela en tant que boucle while.

  1. Voici la sortie :
Jessica
Henry
Alicia
Jose

Une opération courante sur les listes consiste à effectuer une action sur chaque élément.

  1. Modifiez la boucle de manière à imprimer également le nombre de caractères qui composent le nom de cette personne. Conseil : vous pouvez utiliser la propriété length d'une String pour connaître le nombre de caractères de cetteString.
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
    println("$name - Number of characters: ${name.length}")
}

Sortie :

Jessica - Number of characters: 7
Henry - Number of characters: 5
Alicia - Number of characters: 6
Jose - Number of characters: 4

Le code de la boucle n'a pas modifié l'élément List d'origine. Seul le contenu imprimé a été affecté.

Ce qui est vraiment génial, c'est que vous pouvez écrire des instructions pour les actions à effectuer pour un élément de liste et que le code est exécuté pour tous les éléments. Une boucle peut vous dispenser de la saisie d'une grande quantité de code répétitif.

Maintenant que vous vous êtes essayé à la création et l'utilisation de listes et de listes modifiables, et que vous avez découvert les boucles, il est temps d'appliquer ces connaissances à un exemple de cas d'utilisation.

5. Mise en pratique

Lorsque vous commandez un repas dans un restaurant près de chez vous, la commande en question contient généralement plusieurs articles. L'utilisation de listes est idéale pour stocker des informations sur une commande. Vous exploitez également vos connaissances des classes et de l'héritage pour créer un programme Kotlin plus efficace et évolutif, au lieu de placer tout le code dans la fonction main().

Pour la série de tâches suivante, vous allez créer un programme Kotlin permettant de commander différentes combinaisons d'aliments.

Examinons d'abord cet exemple de sortie du code final. Pourriez-vous réfléchir aux types de classes à créer pour organiser toutes ces données ?

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Voici quelques observations concernant la sortie ci-dessous :

  • Il y a une liste de commandes
  • Chaque commande est associée à un numéro
  • Chaque commande peut contenir une liste d'aliments (nouilles, légumes, etc.)
  • Chaque article a un prix
  • Chaque commande a un prix total, qui correspond à la somme des prix de chaque article

Vous pouvez créer une classe pour représenter un Order et une classe pour chaque aliment, comme Noodles ou Vegetables. Vous pouvez également observer que Noodles et Vegetables présentent quelques similitudes, car ce sont des aliments et qu'ils ont chacun un prix. Vous pouvez envisager de créer une classe Item avec des propriétés partagées dont les classes Noodle et Vegetable peuvent hériter. Cela vous évite de devoir dupliquer la logique dans les classes Noodle et Vegetable.

  1. Le code de démarrage suivant est fourni. Il arrive fréquemment que les développeurs professionnels doivent lire le code d'autres personnes, par exemple lorsqu'ils rejoignent un nouveau projet ou complètent une fonctionnalité créée par quelqu'un d'autre. Être capable de lire et de comprendre le code est une compétence importante.

Prenez le temps d'examiner ce code et de comprendre ce qui se passe. Copiez et collez ce code dans Kotlin Playground, puis exécutez-le. Veillez à supprimer tout code existant dans Kotlin Playground avant de coller ce nouveau code. Examinez la sortie et voyez si cela vous aide à mieux comprendre le code.

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10)

class Vegetables : Item("Vegetables", 5)

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  1. Vous devriez obtenir une sortie semblable à ceci :
Noodles@5451c3a8
Vegetables@76ed5528

Voici une explication plus détaillée du code. Il y a tout d'abord une classe appelée Item, dans laquelle le constructeur accepte deux paramètres : un name pour l'élément (String, par exemple) et un price (sous la forme d'un nombre entier). Les deux propriétés ne sont pas modifiées après avoir été transmises. Elles sont donc marquées comme val. Comme Item est une classe parente à partir de laquelle les sous-classes sont étendues, la classe est marquée avec le mot clé open.

Le constructeur de classe Noodles n'accepte aucun paramètre, mais s'étend à partir de Item et appelle le constructeur de super-classe en transmettant "Noodles" comme nom et un prix de 10. La classe Vegetables est similaire, mais elle appelle le constructeur de super-classe avec "Vegetables" et un prix de 5.

La fonction main() initialise les nouvelles instances d'objet des classes Noodles et Vegetables, puis les imprime dans la sortie.

Ignorer la méthode toString()

Lorsque vous imprimez une instance d'objet dans la sortie, la méthode toString() de l'objet est appelée. En langage Kotlin, chaque classe hérite automatiquement de la méthode toString(). L'implémentation par défaut de cette méthode renvoie simplement le type d'objet avec une adresse mémoire pour l'instance. Vous devez ignorer toString() pour renvoyer quelque chose de plus explicite et convivial que Noodles@5451c3a8 et Vegetables@76ed5528.

  1. Dans la classe Noodles, ignorez la méthode toString() et faites en sorte qu'elle renvoie name. N'oubliez pas que Noodles hérite de la propriété name de sa classe parente Item.
class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}
  1. Répétez la même opération pour la classe Vegetables.
class Vegetables() : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}
  1. Exécutez votre code. Vous obtenez alors un meilleur résultat :
Noodles
Vegetables

À l'étape suivante, vous allez modifier le constructeur de classe Vegetables afin d'intégrer certains paramètres, puis mettre à jour la méthode toString() pour tenir compte de ces informations supplémentaires.

Personnaliser les légumes dans une commande

Pour augmenter l'intérêt des clients pour les soupes de nouilles, vous pouvez inclure différents légumes dans vos commandes.

  1. Dans la fonction main(), au lieu d'initialiser une instance Vegetables sans argument d'entrée, transmettez des types de légumes spécifiques que le client apprécie.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

Si vous essayez de compiler votre code maintenant, l'erreur suivante est renvoyée :

Too many arguments for public constructor Vegetables() defined in Vegetables

Vous transmettez à présent trois arguments String au constructeur de classe Vegetables. Vous devrez donc modifier la classe Vegetables.

  1. Mettez à jour l'en-tête de classe Vegetables pour qu'il intègre trois paramètres de chaîne, comme indiqué dans le code suivant :
class Vegetables(val topping1: String,
                 val topping2: String,
                 val topping3: String) : Item ("Vegetables", 5) {
  1. Une nouvelle compilation de votre code est effectuée. Cependant, cette solution ne fonctionne que si vos clients souhaitent toujours commander exactement trois légumes. Si un client souhaite commander un ou cinq légumes, il ne pourra pas le faire.
  2. Au lieu d'utiliser une propriété pour chaque légume, vous pouvez résoudre le problème en acceptant une liste de légumes (de n'importe quelle taille) dans le constructeur de la classe Vegetables. List ne doit contenir que Strings. Le paramètre d'entrée est donc de type List<String>.
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {

Ce n'est certes pas la solution la plus judicieuse car, dans main(), vous devez modifier votre code pour créer une liste de garnitures avant de la transmettre au constructeur Vegetables.

Vegetables(listOf("Cabbage", "Sprouts", "Onion"))

Il existe une meilleure solution au problème.

  1. En langage Kotlin, le modificateur vararg vous permet de transmettre un nombre variable d'arguments du même type à une fonction ou un constructeur. De cette façon, vous pouvez fournir les différents légumes en tant que chaînes individuelles plutôt que sous la forme d'une liste.

Modifiez la définition de classe de Vegetables pour utiliser un vararg toppings de type String.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
  1. Ce code dans la fonction main() est maintenant opérationnel. Vous pouvez créer une instance Vegetables en transmettant un nombre indéfini de chaînes de garniture.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}
  1. Modifiez maintenant la méthode toString() de la classe Vegetables afin qu'elle renvoie une String qui mentionne également les garnitures au format suivant : Vegetables Cabbage, Sprouts, Onion.

Commencez par le nom de l'élément (Vegetables). Utilisez ensuite la méthode joinToString() pour joindre toutes les garnitures dans une seule chaîne. Joignez ces deux parties à l'aide de l'opérateur + en les séparant par un espace.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        return name + " " + toppings.joinToString()
    }
}
  1. Exécutez votre programme. La sortie doit se présenter comme suit :
Noodles
Vegetables Cabbage, Sprouts, Onion
  1. Lorsque vous écrivez des programmes, vous devez prendre en compte toutes les entrées possibles. En l'absence d'arguments d'entrée dans le constructeur Vegetables, traitez la méthode toString() d'une manière plus conviviale.

Puisque le client veut des légumes, mais qu'il n'a pas dit lesquels, une solution consiste à lui proposer par défaut la sélection du chef.

Mettez à jour la méthode toString() pour renvoyer Vegetables Chef's Choice si aucune garniture n'est transmise. Utilisez la méthode isEmpty() que vous avez étudiée précédemment.

override fun toString(): String {
    if (toppings.isEmpty()) {
        return "$name Chef's Choice"
    } else {
        return name + " " + toppings.joinToString()
    }
}
  1. Mettez à jour la fonction main() pour tester les deux possibilités de création d'une instance Vegetables, c'est-à-dire sans aucun argument de constructeur et avec plusieurs arguments.
fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    val vegetables2 = Vegetables()
    println(noodles)
    println(vegetables)
    println(vegetables2)
}
  1. Vérifiez que le résultat est conforme à vos attentes.
Noodles
Vegetables Cabbage, Sprouts, Onion
Vegetables Chef's Choice

Créer une commande

Maintenant que vous disposez d'un choix d'aliments, vous pouvez créer une commande. Encapsulez la logique d'une commande dans une classe Order de votre programme.

  1. Réfléchissez aux propriétés et méthodes qui sont pertinentes pour la classe Order. Si cela peut vous aider, voici à nouveau un exemple de sortie du code final.
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25
  1. Vous avez peut-être pensé aux éléments suivants :

Classe de commande

Propriétés : numéro de commande, liste d'éléments

Méthodes : ajouter un élément, ajouter plusieurs éléments, imprimer le récapitulatif de la commande (y compris le prix)

  1. En commençant par les propriétés, quel devrait être le type de données de chacune d'elles ? Doivent-elles être publiques ou privées pour la classe ? Doivent-elles être transmises en tant qu'arguments ou définies dans la classe ?
  2. Nous vous proposons ici une solution, mais sachez qu'il existe plusieurs façons de faire. Créez une Order class ayant un paramètre de constructeur orderNumber entier.
class Order(val orderNumber: Int)
  1. Étant donné que vous ne connaissez peut-être pas à l'avance tous les éléments de la commande, n'exigez pas que la liste d'éléments soit transmise en tant qu'argument. Au lieu de cela, vous pouvez la déclarer en tant que variable de classe de niveau supérieur, puis l'initialiser en tant que MutableList vide pouvant contenir des éléments de type Item. Marquez la variable private afin que seule cette classe puisse modifier directement cette liste d'éléments. Cela empêchera toute modification inattendue de la liste par du code en dehors de cette classe.
class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()
}
  1. Vous pouvez également ajouter les méthodes à la définition de classe. N'hésitez pas à choisir des noms pertinents pour chaque méthode. Pour le moment, vous pouvez laisser la logique d'implémentation vide pour chaque méthode. Déterminez également quels arguments de fonction et valeurs de retour sont nécessaires.
class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
   }

   fun addAll(newItems: List<Item>) {
   }

   fun print() {
   }
}
  1. Comme la méthode addItem() semble être la plus simple, commencez par implémenter cette fonction. Elle utilise un nouvel Item que la méthode doit ajouter à itemList.
fun addItem(newItem: Item) {
    itemList.add(newItem)
}
  1. Implémentez ensuite la méthode addAll(). Elle utilise une liste d'éléments en lecture seule. Ajoutez tous ces éléments à la liste interne.
fun addAll(newItems: List<Item>) {
    itemList.addAll(newItems)
}
  1. Ensuite, implémentez la méthode print() qui imprime un récapitulatif de tous les éléments et leurs prix dans la sortie, ainsi que le prix total de la commande.

Commencez par imprimer le numéro de commande. Utilisez ensuite une boucle pour parcourir tous les éléments de la liste. Imprimez chaque élément et son prix. Conservez également le prix total provisoire et continuez à l'incrémenter à mesure que vous itérez la liste. Imprimez le prix total à la fin. Essayez d'implémenter cette logique vous-même. Si vous avez besoin d'aide, consultez la solution ci-dessous.

Vous pouvez inclure le symbole monétaire pour faciliter la lecture de la sortie. Voici un moyen d'implémenter la solution. Ce code utilise le symbole "$", mais n'hésitez pas à utiliser le symbole de votre devise locale.

fun print() {
    println("Order #${orderNumber}")
    var total = 0
    for (item in itemList) {
        println("${item}: $${item.price}")
        total += item.price
    }
    println("Total: $${total}")
}

Pour chaque item de itemList, imprimez l'item (ce qui déclenche l'appel de toString() sur item) suivi du price de l'article. Avant la boucle, initialisez également une variable entière totalsur 0. Continuez ensuite à incrémenter le total en ajoutant le prix de l'élément actuel à total.

Créer des commandes

  1. Testez votre code en créant des instances Order dans la fonction main(). Supprimez d'abord le contenu actuel de votre fonction main().
  2. Vous pouvez utiliser ces exemples de commande ou créer les vôtres. Essayez différentes combinaisons d'éléments dans les commandes en veillant à tester tous les chemins dans votre code. Par exemple, testez les méthodes addItem() et addAll() dans la classe Order, créez des instances Vegetables avec et sans arguments, etc.
fun main() {
    val order1 = Order(1)
    order1.addItem(Noodles())
    order1.print()

    println()

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    order2.print()

    println()

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    order3.print()
}
  1. La sortie du code ci-dessus doit se présenter comme suit. Assurez-vous que le prix total augmente correctement.
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Bravo ! Maintenant, cela ressemble vraiment à une commande de repas.

6. Améliorer votre code

Conserver une liste de commandes

Si vous deviez créer un programme destiné à être utilisé dans un restaurant de nouilles, il serait judicieux de conserver une liste de toutes les commandes passées par les clients.

  1. Créez une liste pour y stocker toutes les commandes. S'agit-il d'une liste en lecture seule ou d'une liste modifiable ?
  2. Ajoutez ce code à la fonction main(). Initialisez la liste pour qu'elle soit vide au début. Ensuite, une fois chaque commande créée, ajoutez-la à la liste.
fun main() {
    val ordersList = mutableListOf<Order>()

    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)
}

Étant donné que les commandes sont ajoutées au fil du temps, la liste doit être une MutableList de type Order. Utilisez ensuite la méthode add() sur MutableList pour ajouter chaque commande.

  1. Une fois que vous disposez d'une liste de commandes, vous pouvez utiliser une boucle pour imprimer chacune d'elles. Imprimez une ligne vide entre les commandes pour faciliter la lecture de la sortie.
fun main() {
    val ordersList = mutableListOf<Order>()

    ...

    for (order in ordersList) {
        order.print()
        println()
    }
}

Cela supprime le code en double dans notre fonction main() et rend le code plus facile à lire. La sortie doit être identique à celle obtenue précédemment.

Implémenter un schéma Builder pour les commandes

Pour que votre code Kotlin soit plus concis, utilisez le schéma Builder lors de la création de commandes. Builder est un schéma de conception en programmation qui vous permet de créer un objet complexe, étape par étape.

  1. Au lieu de renvoyer Unit (ou rien) à partir des méthodes addItem() et addAll() de la classe Order, renvoyez la Order modifiée. Kotlin fournit le mot clé this pour référencer l'instance d'objet actuelle. Dans les méthodes addItem() et addAll(), la valeur Order actuelle est renvoyée avec this.
fun addItem(newItem: Item): Order {
    itemList.add(newItem)
    return this
}

fun addAll(newItems: List<Item>): Order {
    itemList.addAll(newItems)
    return this
}
  1. Dans la fonction main(), vous pouvez désormais enchaîner les appels, comme indiqué dans le code suivant. Ce code crée un Order et tire parti du modèle Builder.
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)

Order(4) renvoie une instance Order, sur laquelle vous pouvez ensuite appeler addItem(Noodles()). La méthode addItem() renvoie la même instance Order (avec le nouvel état) et vous pouvez à nouveau appeler addItem() sur celle-ci avec des légumes. Le résultat Order renvoyé peut être stocké dans la variable order4.

Le code actuel permettant de créer Orders fonctionne toujours et peut donc être conservé tel quel. Bien qu'il ne soit pas obligatoire d'enchaîner ces appels, il s'agit d'une pratique courante et recommandée qui tire parti de la valeur renvoyée par la fonction.

  1. À ce stade, il n'est même pas nécessaire de stocker la commande dans une variable. Dans la fonction main() (avant la boucle finale utilisée pour imprimer les commandes), créez directement une Order et ajoutez-la à orderList. Le code est également plus facile à lire si chaque appel de méthode est placé sur sa propre ligne.
ordersList.add(
    Order(5)
        .addItem(Noodles())
        .addItem(Noodles())
        .addItem(Vegetables("Spinach")))
  1. Exécutez votre code. Voici la sortie attendue :
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25

Félicitations, vous avez terminé cet atelier de programmation !

Vous savez maintenant à quel point il peut être utile de stocker des données dans des listes, de modifier des listes et de les lire en boucle. Appliquez ces connaissances dans le contexte d'une application Android pour afficher une liste de données à l'écran dans le prochain atelier de programmation.

7. Code de solution

Voici le code de solution pour les classes Item, Noodles, Vegetables et Order. La fonction main() montre également comment utiliser ces classes. Il existe plusieurs façons d'implémenter ce programme. Votre code peut donc être légèrement différent.

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
    override fun toString(): String {
        return name
    }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()

    fun addItem(newItem: Item): Order {
        itemList.add(newItem)
        return this
    }

    fun addAll(newItems: List<Item>): Order {
        itemList.addAll(newItems)
        return this
    }

    fun print() {
        println("Order #${orderNumber}")
        var total = 0
        for (item in itemList) {
            println("${item}: $${item.price}")
            total += item.price
        }
        println("Total: $${total}")
    }
}

fun main() {
    val ordersList = mutableListOf<Order>()

    // Add an item to an order
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    // Add multiple items individually
    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    // Add a list of items at one time
    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    // Use builder pattern
    val order4 = Order(4)
        .addItem(Noodles())
        .addItem(Vegetables("Cabbage", "Onion"))
    ordersList.add(order4)

    // Create and add order directly
    ordersList.add(
        Order(5)
            .addItem(Noodles())
            .addItem(Noodles())
            .addItem(Vegetables("Spinach"))
    )

    // Print out each order
    for (order in ordersList) {
        order.print()
        println()
    }
}

8. Résumé

Kotlin propose des fonctionnalités qui vous permettent de gérer et de manipuler plus facilement des collections de données via la bibliothèque standard Kotlin. Une collection peut être définie comme un nombre d'objets ayant le même type de données. Il existe différents types de collection de base en langage Kotlin : les listes, les ensembles et les cartes. Cet atelier de programmation porte plus spécifiquement sur les listes. Vous en apprendrez davantage sur les ensembles et les cartes dans les prochains ateliers.

  • Une liste est un ensemble ordonné d'éléments d'un certain type ; une liste de Strings., par exemple.
  • L'index est la position entière qui reflète la position de l'élément (par exemple, myList[2]).
  • Dans une liste, le premier élément se trouve au niveau de l'index 0 (par exemple, myList[0]) et le dernier au niveau de myList.size-1 (par exemple, myList[myList.size-1] ou myList.last()).
  • Il existe deux types de listes : List et MutableList.
  • Une List est en lecture seule et ne peut pas être modifiée une fois qu'elle a été initialisée. Vous pouvez toutefois appliquer des opérations telles que sorted() et reversed(), qui renvoient une nouvelle liste sans modifier celle d'origine.
  • Une MutableList peut être modifiée après sa création, par exemple en ajoutant, en supprimant ou en modifiant des éléments.
  • Vous pouvez ajouter une liste d'éléments à une liste modifiable à l'aide de addAll().
  • Utilisez une boucle while pour exécuter un bloc de code jusqu'à ce que l'expression prenne la valeur "false" et que vous quittiez la boucle.

while (expression) {

// While the expression is true, execute this code block

}

  • Utilisez une boucle for pour itérer tous les éléments d'une liste :

for (item in myList) {

// Execute this code block for each element of the list

}

  • Le modificateur vararg vous permet de transmettre un nombre variable d'arguments à une fonction ou un constructeur.

9. En savoir plus