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
Il s'agit du dernier atelier de programmation de la formation Kotlin. Dans cet atelier de programmation, vous découvrirez les annotations et les arrêts libellés. Vous étudierez les lambdas et les fonctions d'ordre supérieur, qui sont des éléments clés de Kotlin. Vous en apprendrez également davantage sur les fonctions d'intégration et les interfaces SAM (Single Abstract Method). Enfin, vous en apprendrez plus sur la bibliothèque standard Kotlin.
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
- Créer une classe dans IntelliJ IDEA et exécuter un programme
- Principes de base des lambdas et des fonctions d'ordre supérieur
Points abordés
- Principes de base des annotations
- Utiliser les pauses avec libellé
- En savoir plus sur les fonctions d'ordre supérieur
- À propos des interfaces SAM (Single Abstract Method)
- À propos de la bibliothèque standard Kotlin
Objectifs de l'atelier
- Créez une annotation simple.
- Utilisez un saut avec libellé.
- Examiner les fonctions lambda en Kotlin
- Utiliser et créer des fonctions d'ordre supérieur
- Appelez certaines interfaces de méthode abstraite unique.
- Utilisez certaines fonctions de la bibliothèque standard Kotlin.
Les annotations permettent d'associer des métadonnées au code. Elles ne sont pas spécifiques à Kotlin. Les annotations sont lues par le compilateur et utilisées pour générer du code ou de la logique. De nombreux frameworks, tels que Ktor et Kotlinx, ainsi que Room, utilisent des annotations pour configurer leur exécution et leur interaction avec votre code. Il est peu probable que vous rencontriez des annotations avant de commencer à utiliser des frameworks, mais il est utile de savoir comment les lire.
La bibliothèque standard Kotlin propose également des annotations qui contrôlent la façon dont le code est compilé. Elles sont très utiles si vous exportez du code Kotlin vers du code Java, mais vous n'en aurez pas souvent besoin dans les autres cas.
Les annotations se placent juste avant l'élément annoté. La plupart des éléments peuvent être annotés : classes, fonctions, méthodes et même structures de contrôle. Certaines annotations peuvent recevoir des arguments.
Voici un exemple d'annotations.
@file:JvmName("InteropFish")
class InteropFish {
companion object {
@JvmStatic fun interop()
}
}Cela signifie que le nom exporté de ce fichier est InteropFish avec l'annotation JvmName, qui prend un argument de "InteropFish".JvmName Dans l'objet compagnon, @JvmStatic indique à Kotlin de faire de interop() une fonction statique dans InteropFish.
Vous pouvez également créer vos propres annotations, mais cela est surtout utile si vous écrivez une bibliothèque qui a besoin d'informations spécifiques sur les classes au moment de l'exécution, c'est-à-dire la réflexion.
Étape 1 : Créez un package et un fichier
- Sous src, créez un package,
example. - Dans example, créez un fichier Kotlin,
Annotations.kt.
Étape 2 : Créez votre propre annotation
- Dans
Annotations.kt, créez une classePlantavec deux méthodes,trim()etfertilize().
class Plant {
fun trim(){}
fun fertilize(){}
}- Créez une fonction qui affiche toutes les méthodes d'une classe. Utilisez
::classpour obtenir des informations sur une classe au moment de l'exécution. UtilisezdeclaredMemberFunctionspour obtenir la liste des méthodes d'une classe. (Pour y accéder, vous devez importerkotlin.reflect.full.*.)
import kotlin.reflect.full.* // required import
class Plant {
fun trim(){}
fun fertilize(){}
}
fun testAnnotations() {
val classObj = Plant::class
for (m in classObj.declaredMemberFunctions) {
println(m.name)
}
}- Créez une fonction
main()pour appeler votre routine de test. Exécutez votre programme et observez le résultat.
fun main() {
testAnnotations()
}⇒ trim fertilize
- Créez une annotation simple,
ImAPlant.
annotation class ImAPlantCette opération ne fait rien d'autre que d'indiquer que l'élément est annoté.
- Ajoutez l'annotation devant votre classe
Plant.
@ImAPlant class Plant{
...
}- Modifiez
testAnnotations()pour imprimer toutes les annotations d'une classe. Utilisezannotationspour obtenir toutes les annotations d'une classe. Exécutez votre programme et observez le résultat.
fun testAnnotations() {
val plantObject = Plant::class
for (a in plantObject.annotations) {
println(a.annotationClass.simpleName)
}
}⇒ ImAPlant
- Modifiez
testAnnotations()pour trouver l'annotationImAPlant. UtilisezfindAnnotation()pour trouver une annotation spécifique. Exécutez votre programme et observez le résultat.
fun testAnnotations() {
val plantObject = Plant::class
val myAnnotationObject = plantObject.findAnnotation<ImAPlant>()
println(myAnnotationObject)
}
⇒ @example.ImAPlant()
Étape 3 : Créez une annotation ciblée
Les annotations peuvent cibler des getters ou des setters. Dans ce cas, vous pouvez les appliquer avec le préfixe @get: ou @set:. Cela se produit souvent lorsque vous utilisez des frameworks avec des annotations.
- Déclarez deux annotations,
OnGetqui ne peuvent être appliquées qu'aux getters de propriété, etOnSetqui ne peuvent être appliquées qu'aux setters de propriété. Utilisez@Target(AnnotationTarger.PROPERTY_GETTER)ouPROPERTY_SETTERsur chacun.
annotation class ImAPlant
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class OnGet
@Target(AnnotationTarget.PROPERTY_SETTER)
annotation class OnSet
@ImAPlant class Plant {
@get:OnGet
val isGrowing: Boolean = true
@set:OnSet
var needsFood: Boolean = false
}Les annotations sont très utiles pour créer des bibliothèques qui inspectent les éléments à l'exécution et parfois à la compilation. Toutefois, le code d'application typique n'utilise que les annotations fournies par les frameworks.
Kotlin propose plusieurs façons de contrôler le flux. Vous connaissez déjà return, qui renvoie une fonction à sa fonction englobante. L'utilisation d'un break est semblable à celle d'un return, mais pour les boucles.
Kotlin vous offre un contrôle supplémentaire sur les boucles grâce à ce qu'on appelle une instruction break avec libellé. Un break qualifié avec un libellé passe au point d'exécution juste après la boucle marquée avec ce libellé. Cela est particulièrement utile lorsque vous traitez des boucles imbriquées.
Toute expression en Kotlin peut être marquée avec un libellé. Les libellés se présentent sous la forme d'un identifiant suivi du signe @.
- Dans
Annotations.kt, essayez une pause étiquetée en sortant d'une boucle interne.
fun labels() {
outerLoop@ for (i in 1..100) {
print("$i ")
for (j in 1..100) {
if (i > 10) break@outerLoop // breaks to outer loop
}
}
}
fun main() {
labels()
}- Exécutez votre programme et observez le résultat.
⇒ 1 2 3 4 5 6 7 8 9 10 11
De même, vous pouvez utiliser un continue libellé. Au lieu de sortir de la boucle étiquetée, l'instruction continue étiquetée passe à l'itération suivante de la boucle.
Les lambdas sont des fonctions anonymes, c'est-à-dire des fonctions sans nom. Vous pouvez les attribuer à des variables et les transmettre en tant qu'arguments à des fonctions et des méthodes. Elles sont extrêmement utiles.
Étape 1 : Créez une lambda simple
- Démarrez le REPL dans IntelliJ IDEA : Tools > Kotlin > Kotlin REPL.
- Créez un lambda avec un argument,
dirty: Int, qui effectue un calcul en divisantdirtypar 2. Attribuez le lambda à une variable,waterFilter.
val waterFilter = { dirty: Int -> dirty / 2 }- Appelez
waterFilteren transmettant une valeur de 30.
waterFilter(30)⇒ res0: kotlin.Int = 15
Étape 2 : Créez une fonction lambda de filtre
- Toujours dans la REPL, créez une classe de données,
Fish, avec une propriété,name.
data class Fish(val name: String)- Crée une liste de trois
Fish, avec les noms Flipper, Moby Dick et Dory.
val myFish = listOf(Fish("Flipper"), Fish("Moby Dick"), Fish("Dory"))- Ajoutez un filtre pour rechercher les noms contenant la lettre "i".
myFish.filter { it.name.contains("i")}
⇒ res3: kotlin.collections.List<Line_1.Fish> = [Fish(name=Flipper), Fish(name=Moby Dick)]
Dans l'expression lambda, it fait référence à l'élément de liste actuel, et le filtre est appliqué à chaque élément de liste à son tour.
- Appliquez
joinString()au résultat, en utilisant", "comme séparateur.
myFish.filter { it.name.contains("i")}.joinToString(", ") { it.name }
⇒ res4: kotlin.String = Flipper, Moby Dick
La fonction joinToString() crée une chaîne en joignant les noms filtrés, séparés par la chaîne spécifiée. Il s'agit de l'une des nombreuses fonctions utiles intégrées à la bibliothèque standard Kotlin.
Le fait de transmettre une fonction lambda ou une autre fonction en tant qu'argument à une fonction crée une fonction d'ordre supérieur. Le filtre ci-dessus en est un exemple simple. filter() est une fonction à laquelle vous transmettez un lambda qui spécifie comment traiter chaque élément de la liste.
L'écriture de fonctions d'ordre supérieur avec des lambdas d'extension est l'une des parties les plus avancées du langage Kotlin. Il faut un certain temps pour apprendre à les écrire, mais ils sont très pratiques à utiliser.
Étape 1 : Créer un cours
- Dans le package example, créez un fichier Kotlin,
Fish.kt. - Dans
Fish.kt, créez une classe de donnéesFishavec une propriété,name.
data class Fish (var name: String)- Créez une fonction
fishExamples(). DansfishExamples(), créez un poisson nommé"splashy", en minuscules.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
}- Créez une fonction
main()qui appellefishExamples().
fun main () {
fishExamples()
}- Compilez et exécutez votre programme en cliquant sur le triangle vert à gauche de
main(). Aucun résultat n'est encore disponible.
Étape 2 : Utiliser une fonction d'ordre supérieur
La fonction with() vous permet de faire une ou plusieurs références à un objet ou à une propriété de manière plus compacte. Avec this with() est en fait une fonction d'ordre supérieur. Dans le lambda, vous spécifiez ce qu'il faut faire avec l'objet fourni.
- Utilisez
with()pour mettre en majuscule le nom du poisson dansfishExamples(). Entre les accolades,thisfait référence à l'objet transmis àwith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
this.capitalize()
}
}- Aucun résultat n'est produit. Ajoutez donc un
println()autour. De plus, lethisest implicite et n'est pas nécessaire. Vous pouvez donc le supprimer.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
println(capitalize())
}
}⇒ Splashy
Étape 3 : Créez une fonction d'ordre supérieur
En arrière-plan, with() est une fonction d'ordre supérieur. Pour voir comment cela fonctionne, vous pouvez créer votre propre version très simplifiée de with() qui ne fonctionne que pour les chaînes.
- Dans
Fish.kt, définissez une fonction,myWith(), qui prend deux arguments. Les arguments sont l'objet sur lequel effectuer des opérations et une fonction qui définit l'opération. La convention pour le nom de l'argument avec la fonction estblock. Dans ce cas, cette fonction ne renvoie rien, ce qui est spécifié avecUnit.
fun myWith(name: String, block: String.() -> Unit) {}Dans myWith(), block() est désormais une fonction d'extension de String. La classe étendue est souvent appelée objet récepteur. Dans ce cas, name est l'objet récepteur.
- Dans le corps de
myWith(), appliquez la fonction transmise,block(), à l'objet récepteur,name.
fun myWith(name: String, block: String.() -> Unit) {
name.block()
}- Dans
fishExamples(), remplacezwith()parmyWith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
myWith (fish.name) {
println(capitalize())
}
}fish.name est l'argument de nom et println(capitalize()) est la fonction de bloc.
- Exécutez le programme. Il fonctionne comme avant.
⇒ Splashy
Étape 4 : Découvrez d'autres extensions intégrées
Le lambda d'extension with() est très utile et fait partie de la bibliothèque standard Kotlin. Voici quelques-unes des autres options qui pourraient vous être utiles : run(), apply() et let().
La fonction run() est une extension qui fonctionne avec tous les types. Il prend un lambda comme argument et renvoie le résultat de l'exécution du lambda.
- Dans
fishExamples(), appelezrun()surfishpour obtenir le nom.
fish.run {
name
}Cela renvoie simplement la propriété name. Vous pouvez l'attribuer à une variable ou l'imprimer. Cet exemple n'est pas très utile, car vous pouvez simplement accéder à la propriété, mais run() peut être utile pour les expressions plus complexes.
La fonction apply() est semblable à run(), mais elle renvoie l'objet modifié auquel elle a été appliquée au lieu du résultat du lambda. Cela peut être utile pour appeler des méthodes sur un objet nouvellement créé.
- Créez une copie de
fishet appelezapply()pour définir le nom de la nouvelle copie.
val fish2 = Fish(name = "splashy").apply {
name = "sharky"
}
println(fish2.name)
⇒ sharky
La fonction let() est semblable à apply(), mais elle renvoie une copie de l'objet avec les modifications. Cela peut être utile pour enchaîner les manipulations.
- Utilisez
let()pour obtenir le nom defish, mettez-le en majuscules, concaténez-le avec une autre chaîne, obtenez la longueur de ce résultat, ajoutez 31 à la longueur, puis affichez le résultat.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})⇒ 42
Dans cet exemple, le type d'objet auquel it fait référence est Fish, puis String, puis à nouveau String et enfin Int.
- Imprimez
fishaprès avoir appelélet(). Vous verrez qu'il n'a pas changé.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})
println(fish)⇒ 42 Fish(name=splashy)
Les lambdas et les fonctions d'ordre supérieur sont très utiles, mais il y a une chose à savoir : les lambdas sont des objets. Une expression lambda est une instance d'une interface Function, qui est elle-même un sous-type de Object. Prenons l'exemple précédent de myWith().
myWith(fish.name) {
capitalize()
}L'interface Function possède une méthode, invoke(), qui est remplacée pour appeler l'expression lambda. Écrit en toutes lettres, il ressemblerait au code ci-dessous.
// actually creates an object that looks like this
myWith(fish.name, object : Function1<String, Unit> {
override fun invoke(name: String) {
name.capitalize()
}
})Normalement, cela ne pose pas de problème, car la création d'objets et l'appel de fonctions n'entraînent pas beaucoup de surcharge (mémoire et temps processeur). Toutefois, si vous définissez un élément comme myWith() que vous utilisez partout, la surcharge peut s'accumuler.
Kotlin fournit inline pour gérer ce cas et réduire la surcharge lors de l'exécution en ajoutant un peu plus de travail pour le compilateur. (Vous avez découvert inline dans la leçon précédente sur les types concrets.) Marquer une fonction comme inline signifie que chaque fois que la fonction est appelée, le compilateur transforme le code source pour "intégrer" la fonction. Autrement dit, le compilateur modifiera le code pour remplacer le lambda par les instructions qu'il contient.
Si myWith() dans l'exemple ci-dessus est marqué avec inline :
inline myWith(fish.name) {
capitalize()
}il est transformé en appel direct :
// with myWith() inline, this becomes
fish.name.capitalize()Il est important de noter que l'intégration de fonctions volumineuses augmente la taille de votre code. Il est donc préférable de l'utiliser pour des fonctions simples qui sont utilisées plusieurs fois, comme myWith(). Les fonctions d'extension des bibliothèques que vous avez découvertes précédemment sont marquées inline. Vous n'avez donc pas à vous soucier de la création d'objets supplémentaires.
"Single Abstract Method" signifie simplement une interface avec une seule méthode. Elles sont très courantes lorsque vous utilisez des API écrites dans le langage de programmation Java. Il existe même un acronyme pour les désigner : SAM. Par exemple, Runnable comporte une seule méthode abstraite, run(), et Callable comporte une seule méthode abstraite, call().
En Kotlin, vous devez appeler des fonctions qui prennent des SAM comme paramètres en permanence. Essayez l'exemple ci-dessous.
- Dans example, créez une classe Java,
JavaRun, et collez-y le code suivant.
package example;
public class JavaRun {
public static void runNow(Runnable runnable) {
runnable.run();
}
}Kotlin vous permet d'instancier un objet qui implémente une interface en faisant précéder le type de object:. Il est utile pour transmettre des paramètres aux SAM.
- De retour dans
Fish.kt, créez une fonctionrunExample()qui crée unRunnableà l'aide deobject:. L'objet doit implémenterrun()en imprimant"I'm a Runnable".
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
}- Appelez
JavaRun.runNow()avec l'objet que vous avez créé.
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
JavaRun.runNow(runnable)
}- Appelez
runExample()à partir demain()et exécutez le programme.
⇒ I'm a Runnable
C'est beaucoup de travail pour imprimer quelque chose, mais c'est un bon exemple de fonctionnement d'un SAM. Bien sûr, Kotlin propose une façon plus simple de le faire : utiliser un lambda à la place de l'objet pour rendre ce code beaucoup plus compact.
- Supprimez le code existant dans
runExample, remplacez-le par un appel àrunNow()avec une expression lambda, puis exécutez le programme.
fun runExample() {
JavaRun.runNow({
println("Passing a lambda as a Runnable")
})
}
⇒ Passing a lambda as a Runnable
- Vous pouvez rendre cela encore plus concis en utilisant la syntaxe d'appel du dernier paramètre et en supprimant les parenthèses.
fun runExample() {
JavaRun.runNow {
println("Last parameter is a lambda as a Runnable")
}
}⇒ Last parameter is a lambda as a Runnable
Voilà pour les bases d'une SAM (Single Abstract Method). Vous pouvez instancier, remplacer et appeler un SAM avec une seule ligne de code, en utilisant le modèle suivant :Class.singleAbstractMethod { lambda_of_override }
Cette leçon a passé en revue les lambdas et a approfondi les fonctions d'ordre supérieur, qui sont des éléments clés de Kotlin. Vous avez également découvert les annotations et les arrêts libellés.
- Utilisez des annotations pour spécifier des éléments au compilateur. Exemples :
@file:JvmName("Foo") - Utilisez des instructions break avec libellé pour permettre à votre code de sortir des boucles imbriquées. Exemples :
if (i > 10) break@outerLoop // breaks to outerLoop label - Les lambdas peuvent être très puissants lorsqu'ils sont associés à des fonctions d'ordre supérieur.
- Les lambdas sont des objets. Pour éviter de créer l'objet, vous pouvez marquer la fonction avec
inline. Le compilateur insérera alors directement le contenu du lambda dans le code. - Utilisez
inlineavec précaution, mais cela peut aider à réduire l'utilisation des ressources par votre programme. - SAM (Single Abstract Method) est un modèle courant, simplifié avec les lambdas. Le modèle de base est le suivant :
Class.singleAbstractMethod { lamba_of_override } - La bibliothèque standard Kotlin fournit de nombreuses fonctions utiles, y compris plusieurs SAM. Familiarisez-vous avec son contenu.
Il y a beaucoup plus à découvrir sur Kotlin que ce qui a été abordé dans le cours, mais vous avez maintenant les bases pour commencer à développer vos propres programmes Kotlin. Nous espérons que vous êtes enthousiaste à l'idée d'utiliser ce langage expressif et que vous avez hâte de créer plus de fonctionnalités en écrivant moins de code (surtout si vous venez du langage de programmation Java). La meilleure façon de devenir un expert en Kotlin est de pratiquer et d'apprendre au fur et à mesure. Continuez donc à explorer et à découvrir Kotlin par vous-même.
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.
- Annotations
- Réflexion
- Points d'arrêt étiquetés
- Fonctions d'ordre supérieur et lambdas
- Fonctions intégrées
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.
Bibliothèque standard Kotlin
La bibliothèque standard Kotlin fournit de nombreuses fonctions utiles. Avant d'écrire votre propre fonction ou interface, vérifiez toujours la bibliothèque standard pour voir si quelqu'un vous a déjà fait gagner du temps. Consultez-la régulièrement, car de nouvelles fonctionnalités sont ajoutées fréquemment.
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
En Kotlin, SAM signifie :
▢ Correspondance sécurisée des arguments
▢ Méthode d'accès simple
▢ Single Abstract Method
▢ Méthodologie d'accès stratégique
Question 2
Parmi les propositions suivantes, laquelle n'est pas une fonction d'extension de la bibliothèque standard Kotlin ?
▢ elvis()
▢ apply()
▢ run()
▢ with()
Question 3
Parmi les affirmations suivantes concernant les lambdas en Kotlin, laquelle est fausse ?
▢ Les lambdas sont des fonctions anonymes.
▢ Les lambdas sont des objets, sauf s'ils sont intégrés.
▢ Les lambdas nécessitent beaucoup de ressources et ne doivent pas être utilisés.
▢ Les lambdas peuvent être transmis à d'autres fonctions.
Question 4
En Kotlin, les libellés sont indiqués par un identifiant suivi de :
▢ :
▢ ::
▢ @:
▢ @
Félicitations ! Vous avez terminé l'atelier de programmation "Kotlin Bootcamp for Programmers".
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.