Maintenant que vous avez appris les bases du fonctionnement de Kotlin, nous allons pousser l’expertise encore un cran au-dessus et parler plus en détail des fonctions.
Donnons-leur un nom !
Parfois, lorsque nous développons certains programmes, il nous arrive de créer des méthodes possédant un nombre de paramètres assez conséquent ! Prenons par exemple cette méthode écrite en Java :
public void displayRepetitiveMessage(String message, String errorMessage, int repeat, int delay, boolean isSilent){
try{
Thread.sleep(delay);
for(int i = 0; i < repeat; i++) {
if (!isSilent) System.out.println(message + " " + i + " times(s)");
}
} catch (InterruptedException ex){
System.out.println(errorMessage)
}
}
Ensuite appelons-la avec les paramètres nécessaires :
this.displayRepetitiveMessgae("Checking network connection...", "Something went wrong!", 3, 1000, false)
Nous avons créé une méthode possédant 5 paramètres et nous l’avons ensuite appelée juste en dessous.
Si vous reproduisez l’exemple ci-dessus, et tentez d’appeler la méthode displayRepetitiveMessage
, vous allez sûrement jeter un coup d’œil à la signature de celle-ci pour savoir quels sont les paramètres, et surtout dans quel ordre vous allez devoir les renseigner… :)
Et cela n’est pas très productif ! C’est d’ailleurs pour cette raison que Kotlin a décidé de vous faciliter la vie en créant les paramètres nommés (ou "Named arguments").
Reprenons l’exemple précédent, mais en Kotlin, cette fois-ci :
fun displayRepetitiveMessage(message: String, errorMessage: String, repeat: Int, delay: Int, isSilent: Boolean){
try{
Thread.sleep(delay.toLong());
for(i in 0 until repeat) {
if (!isSilent) println("$message $i times(s)");
}
} catch (ex: InterruptedException){
println(errorMessage)
}
}
displayRepetitiveMessage(message = "Checking network connection...", errorMessage = "Something went wrong!", repeat = 3, delay = 1000, isSilent = false)
Comme vous pouvez le voir, le nom du paramètre peut être rajouté juste avant sa valeur. Cela permet, encore une fois, d’améliorer grandement la lisibilité de votre code. ;)
Bon, je sais, beaucoup d’IDE modernes vous indiquent directement le nom des paramètres avant leur emplacement. Cela nous permettra donc de nous passer pour le moment de cette fonctionnalité. Vous aurez cependant eu l’opportunité de savoir qu’elle existait !
![Suggestion des paramètres d'un IDE](https://user.oc-static.com/upload/2023/02/06/16756734790954_1655386050134_p3c1-bis.png)
Par défaut…
Parfois, dans certaines situations, nous sommes amenés en Java à utiliser la surcharge (ou "overloads", en anglais) de méthode (ou même de constructeur) afin de gérer des cas supplémentaires, des incompatibilités logicielles ou tout simplement pour ajouter un paramètre.
Cependant, cela crée très souvent un phénomène de "répétition" de code pas forcément très utile, impactant à terme sa lisibilité.
Reprenons par exemple notre précédente méthode displayRepetitiveMessage
. Lui renseigner à chaque fois ses cinq paramètres peut s’avérer fastidieux, surtout que certains semblent "optionnels". Comment faire si nous souhaitons seulement renseigner un message à cette méthode ?
Eh bien grâce à la surcharge de méthode, je peux écrire quelque chose comme ça ?
fun displayRepetitiveMessage(message: String){
displayRepetitiveMessage(message, errorMessage: "Error", repeat: 3, delay: 0, isSilent: false)
}
fun displayRepetitiveMessage(message: String, errorMessage: String, repeat: Int, delay: Int, isSilent: Boolean){
try{
Thread.sleep(delay.toLong())
for(i in 0 until repeat) {
if (!isSilent) println("$message $i times(s)")
}
} catch (ex: InterruptedException){
println(errorMessage)
}
}
Très bonne solution… si nous étions en Java ! :D Mais nous écrivons maintenant du code en Kotlin. ;) Ainsi en Kotlin, afin de gérer ce cas précis et améliorer la lisibilité de notre code, nous allons pouvoir renseigner… des valeurs par défaut à nos paramètres ! (Ou "Default parameter values", en anglais.)
Grâce à cette fonctionnalité, nous allons pouvoir indiquer dans la signature de notre méthode des valeurs par défaut :
fun displayRepetitiveMessage(message: String, errorMessage: String = "Error", repeat: Int = 3, delay: Int = 0, isSilent: Boolean = false)
displayRepetitiveMessage("Checking network connection...")
displayRepetitiveMessage("Checking network connection...", "Something went wrong!")
displayRepetitiveMessage("Checking network connection...", "Something went wrong!", 10)
Ainsi, nous pourrons maintenant être beaucoup plus flexibles dans le renseignement (ou non) des paramètres d'une même méthode ! :) En combinaison avec les "Named Arguments", nous allons même ne plus du tout nous soucier de l’ordre d’appel des paramètres :
displayRepetitiveMessage("Checking network connection...", isSilent = true, repeat = 10)
Plutôt efficace, non ?
Voyons ce que cela donne sans le @JvmOverloads :
fun displayRepetitiveMessage(message: String, errorMessage: String = "Error", repeat: Int = 3, delay: Int = 0, isSilent: Boolean = false)
Et maintenant, avec le @JvmOverloads :
@JvmOverloads
fun displayRepetitiveMessage(message: String, errorMessage: String = "Error", repeat: Int = 3, delay: Int = 0, isSilent: Boolean = false)
Des fonctions timides
Comme nous avons pu le voir dans la première partie de ce cours, Kotlin essaie d’être un langage de programmation le plus fonctionnel possible. Ainsi, nous allons pouvoir créer des fonctions dites "locales", c’est-à-dire se trouvant à l’intérieur d’une autre fonction.
Reprenons notre précédent exemple, et faisons-le évoluer avec des fonctions locales :
fun displayRepetitiveMessage(message: String, errorMessage: String, repeat: Int, delay: Int, isSilent: Boolean) {
// --- Local functions ---
fun delayFunction() = try {
Thread.sleep(delay.toLong())
} catch (ex: Exception){
println(errorMessage)
}
fun printMessage(i: Int) {
if (!isSilent) println("$message $i time(s)")
}
// --- Logic ---
delayFunction()
for (i in 0 until repeat) printMessage(i)
}
Plutôt intéressant, n’est-ce pas ? :) Les fonctions locales nous permettront de rendre notre code un peu plus lisible dans certains cas (comme celui-ci !), mais aussi de ne pas nous répéter dans d'autres (le fameux principe DRY).
Par exemple, imaginez que vous souhaitiez sécuriser la sauvegarde d’un utilisateur en base de données. Grâce aux fonctions locales, votre code sera bien plus lisible et ordonné. ;)
fun saveUser(user: User){
fun validate(value: String){
if (value.isEmpty()) throw IllegalArgumentException("Field must not be empty!")
}
validate(user.email)
validate(user.password)
validate(user.urlImage)
//... save user in database
}
Le top du top !
En tant que développeur Java, vous devez normalement connaître les différents principes et objectifs de la programmation orientée objet. D’ailleurs, un des principes les plus importants correspond au fameux "S" de l’acronyme SOLID : la responsabilité unique.
Une classe, une fonction ou une méthode se doit d’avoir une et une seule responsabilité, un unique rôle à jouer dans votre code. Ce rôle sera le plus fin possible, mais il devra être remarquablement bien exécuté !
Parfois, cette règle devient complexe à suivre, et il nous arrive malheureusement de créer des classes utilitaires (se terminant généralement par le suffixe Utils
) remplies de méthodes statiques que nous appellerons au gré de nos besoins et envies ! Ces classes jouent en réalité l’unique rôle de "conteneur", ni plus ni moins. Pas très orienté objet, vous ne trouvez pas ?
Non, c’est vrai… Mais quelle solution avons-nous à ce problème ?
Eh bien, en Kotlin, nous allons avoir la notion de "Top-level function" (en français : "fonction de premier niveau"). Dès lors que nous aurons besoin de créer des méthodes potentiellement utilisables à travers l’ensemble de notre programme (pouvant être appelées dans plusieurs classes complètement différentes), nous utiliserons ces fonctions-là. :)
En pratique, imaginons que nous ayons besoin de créer une méthode affichant du texte dans la console d’une manière spécifique et récurrente. En Java, nous aurions créé une classe statique abstraite non instanciable, que nous aurions appelée "LoggingUtils" :
public abstract class LoggingUtils {
private LoggingUtils(){}
public static void logMessage(String message){
Syste.out.println("MY PROGRAM LOGGER" + message);
}
}
Puis nous l’aurions appelée de cette manière :
LoggingUtils.logMessage("This is message!");
Oubliez tout cela en Kotlin ! La notion de classes, méthodes ou même propriétés "statiques" n’existe plus !! :waw:
C’est simple, le mot-clé static
n’est pas supporté par Kotlin. En revanche, on peut dire qu'il a été remplacé par la notion de "top-level" en Kotlin ! :)
Transformons notre précédent exemple en Kotlin afin de mieux comprendre tout cela :
![Fonction de premier niveau (ou](https://user.oc-static.com/upload/2023/02/06/16756735697335_1655387322057_p3c1-1%20%281%29.png)
Tout d’abord, nous avons ajouté un package utils/
comme on pourrait le faire en Java. Puis, à l’intérieur, nous y avons créé (1) un fichier (attention, pas une classe !) appelé log.kt
.
Et enfin, on place à l’intérieur de ce fichier notre précédente fonction (2) écrite cette fois-ci en Kotlin ! :) Finalement, pour l’appeler par la suite dans notre code, nous n’aurons plus qu’à l’importer via un import
(3). Simple et efficace, non ? :D
Trop bien ! Mais comment vais-je faire pour utiliser cette méthode dans une classe en Java ?
Bonne question ! Eh bien le compilateur Kotlin étant très intelligent, ce dernier aura créé automatiquement la classe correspondante en Java qu’il aura appelée LogKt.java
, convertissant également l’ensemble de ses méthodes de Kotlin vers Java.
Vous pourrez ainsi appeler celle-ci en Java comme cela :
![Utilisation d'une méthode de haut-niveau en Java](https://user.oc-static.com/upload/2023/02/06/16756735906796_16553874142016_p3c1-bis2.png)
Pratique, non ? ;)
Essayons pour voir ! :honte:
![Utilisation de @](https://user.oc-static.com/upload/2018/06/06/15283008862371_java_%40jvmName.png)
Mais est-ce que cela fonctionne aussi avec les propriétés ?
Bien sûr ! D’ailleurs, nous avons quasiment tous pris l’habitude de créer une classe Constants
en Java contenant l’ensemble des constantes de notre programme. Nous pourrons en Kotlin réaliser la même chose grâce aux propriétés de premier niveau (ou "top-level properties", en anglais).
![Propriétés de haut-niveau en Kotlin](https://user.oc-static.com/upload/2018/06/06/15283009389306_kotlin_top_level_properties.png)
C’est exactement la même approche que tout à l’heure avec les fonctions. :) Vous créez d’abord un fichier (1) contenant les propriétés que vous souhaitez rendre de "premier niveau" (2), puis vous les récupérez et les utilisez grâce à un simple import (3).
Allez, d’ici quelque temps, vous aurez complètement oublié le mot-clé static
, je vous le promets ! ;)
![Référence : Conan O'brien](https://user.oc-static.com/upload/2018/06/06/15283010173903_cry.gif)
Practice Makes Perfect!
La théorie, c'est bien, mais la pratique, c'est encore mieux ! Justement, nous vous avons concocté un petit exercice interactif de fin de chapitre pour appliquer toutes ces nouvelles connaissances.
🎓Un petit exercice, ça vous tente ? C'est par ici que ça se passe.
🎁Déjà terminé ? Le corrigé se trouve juste ici.
En résumé
En Kotlin, vous pouvez mentionner le nom d’un paramètre (avant sa valeur) directement dans la signature de sa méthode ("Named arguments"). Cela facilite la lecture des méthodes possédant beaucoup de paramètres.
Afin de pallier les problèmes de lisibilité et de copier/coller le code que peut induire la surcharge de méthode/constructeur, Kotlin vous permet d’indiquer une valeur par défaut aux paramètres d’une même méthode.
Une fonction locale (ou "local function") en Kotlin permet de créer une fonction à l’intérieur d’une autre fonction.
Les fonctions et propriétés "premier niveau" remplacent le concept de classes/méthodes statiques en Java utilisées à des fins surtout utilitaires.
Pour aller plus loin :
KotlinLang : Functions