Pré-requis
Adrien Pessu Gautier de Saint Martin Lacaze
Installation de Java 8
Linux x64
RPM
Accéder au site de téléchargement Oracle pour le jdk 8.
Récupérer le RPM suivant jdk-8u161-linux-x64.rpm
Suivez les instructions de votre gestionnaire de paquet.
Changez la version de java à utiliser par votre système avec la commande suivante
alternatives --config java
Sélectionner la version JDK 8 installé précédement
Éventuellement changer également la version de de javac
avec la commande suivante
alternatives --config javac
Il se peut que vous soyez obligé pour la commande alternative d’utiliser sudo
|
Autre
Accéder au site de téléchargement Oracle pour le jdk 8.
Récupérer l’archive suivante jdk-8u161-linux-x64.tar.gz
Désarchiver votre archive où vous le souhaitez.
Par exemple dans /usr/java/jdk1.8.0_161/
Changez la version de java à utiliser avec la commande suivante
alternatives --config java
Sélectionner la version JDK 8 installé précédement
Éventuellement changer également la version de de javac
avec la commande suivante
alternatives --config javac
Il se peut que vous soyez obligé pour la commande alternative d’utiliser sudo
|
Mac
Accéder au site de téléchargement Oracle pour le jdk 8.
Récupérer le fichier suivant jdk-8u161-macosx-x64.dmg
Utiliser le fichier dmg et suivre les instructions d’installation.
Installation d’un IDE
Linux
Par simplicité, nous allons installer Jetbrains Intellij IDEA Community.
Accéder au site de téléchargement
Récupérer l’archive de l’édition community.
Désarchiver l’archive dans un répertoire de votre choix.
Pour lancer l’IDE, il vous suffira d’accéder au répertoire où vous avez installé l’IDE et de lancer le fichier `idea.sh`présent dans le répertoire bin.
Mac
Par simplicité, nous allons installer Jetbrains Intellij IDEA Community.
Accéder au site de téléchargement
Récupérer l’archive dmg de l’édition community.
Utiliser le fichier dmg et suivre les instructions d’installation.
Windows
Par simplicité, nous allons installer Jetbrains Intellij IDEA Community.
Accéder au site de téléchargement
Récupérer l’exécutable de l’édition community.
Exécuter l’installeur et suivre les instructions.
Récupération du projet
En ligne de commande :
git clone https://github.com/bttf-kotlin/kotlin-codelab-bttf.git git checkout step0
Builder l’application
Accéder au répertoire du projet et lancer la commande suivante :
./gradlew build
Les bases
Adrien Pessu Gautier de Saint Martin Lacaze
Contexte
Afin de bien démarrer ce codelab, nous vous avons déjà généré un squelette de projet avec le site https://start.spring.io
Ce site permet d’avoir un projet prêt à l’emploi et nous évitera de perdre du temps dans ce codelab. Nous vous invitons si vous ne le connaissez pas à découvrir ce site après le codelab.
Un peu de théorie
val
vs var
En Kotlin, il existe deux mot clés pour déclarer des variables. Le premier s’appelle val
et le second `var
val
est immutable alors que var
est mutable.
val name = "kotlin"
Dans ce premier cas, on doit obligatoirement initialiser name
et on ne pourra pas changer sa valeur.
Si l’on a besoin de le faire, il faudra utiliser le mot clé var
.
var name: String
name = "kotlin"
Dans ce second cas, grâce au mot clé var
, on peut initialiser name
dans la suite du programme.
On peut remarquer que dans le cas de val le compilateur est capable d’inférer le type de name .
Il n’est pas nécessaire de le préciser.
Dans le cas de var c’est différent. L’initialisation tardive de name empêche le compilateur d’inférer le type.
Il est donc nécessaire de le préciser.
|
Comme vous pouvez le remarquer il n’y a pas besoin de ; à la fin des instructions.
|
Qu’est ce qu’une Class
?
Une classe Kotlin ressemble à une classe Java à quelques différences prêt.
On la déclare avec le mot clé class
class User { }
On peut lui déclarer un ou plusieurs contructeurs. Le premier constructeur sera appelé constructeur primaire. Il est déclaré dans l’entête de la classe.
class User constructor(firstName: String) {
}
Dans le cas où le constructeur n’aurait pas d’annotation, il est possible de réduire la syntaxe du constructeur.
class User (firstName: String) {
}
Vous trouverez plus d’informations si besoin sur le site officiel de Kotlin.
Qu’est ce qu’une Data Class
?
Contrairement à une Class
, une Data Class
représente un POJO contenant de la donnée.
Il a donc pour but de stocker des données dans des attributs et éventuellement de fournir des méthodes pour manipuler le contenu de notre objet.
Pour déclarer une Data Class
, il suffit d’utiliser le mot clé data
conjointement au mot clé class
.
data class User(val name: String, val age: Int)
Comme pour les class , il n’est pas nécessaire de mettre les accolades lorsqu’il n’y a pas de méthodes dans la classe.
|
Là où la Data Class
est très intéressante, c’est que le compilateur va nous aider pour l’avenir en fournissant automatiquement les éléments suivants à notre objet.
-
Les méthodes
equals()
ethashCode()
-
La méthode
toString()
renvoyant une chaîne de charactères de la forme suivanteUser(name=John, age=42)
-
Une méthode de
copy
(évoqué plus tard dans ce codelab) -
Les méthodes dites
componentN()
qui sont utile lors de l’utilisation du destructuring.
Vous trouverez plus d’informations si besoin sur le site officiel de Kotlin.
Exercice 1
Passons maintenant à la pratique.
Si ce n’est pas déjà fait, faites un checkout du step0.
git checkout step0
Aller dans la Data Class
Event
et supprimer l’annotation @JsonIgnoreProperties
.
Lancer l’application via la classe KotlinCodelabBttfApplication
.
Corriger le problème que vous rencontrez.
Comme vous pouvez le remarquer, Event est une Data Class .
La syntaxe est très concise pour créer des POJOs.
|
Un peu de théorie
Les fonctions
Maintenant que nous avons joué rapidement avec notre Data Class
Event
, nous allons présenter les fonctions en Kotlin.
Une fonction Kotlin se déclare de la manière suivant :
fun sum(a: Int, b: Int): Int {
return a + b
}
1 | fun est le mot clé permettant de définir une fonction. Contrairement à Java, une fonction Kotlin a la portée publique par défaut. |
2 | sum est le nom de la fonction. Nous reviendrons sur les noms juste après. |
3 | On peut déclarer ensuite les paramètres d’entrée.
Ici a et b tous les deux de type Int. |
4 | Enfin on indique le type de retour Int |
Comme vous pouvez le remarquer cette fonction est très simple. Il est donc possible en Kotlin de raccourcir la syntaxe d’une telle fonction à une seule ligne. Le code aura alors la forme suivante.
fun sum(a: Int, b: Int) = a + b
Vous trouverez plus d’informations sur les fonctions sur le site officiel de Kotlin.
Il est possible de déclarer une méthode avec des espaces dans le nom. C’est une pratique à réserver aux tests mais qui s’avère bien pratique pour écrire des tests explicites. |
class MyTestCase {
@Test fun `ensure everything works`() {
}
@Test fun ensureEverythingWorks_onAndroid() {
}
}s
Vous trouverez plus d’informations sur les conventions de nommage des fonctions sur cette page
Exercice 2
Dans ce second exercice, nous allons filter automatiquement les événements qui n’ont pas de date dans notre source de données.
Pour cela, accéder à la classe EventController
et ajouter un filtre sur le retour de repository. findAll()
.
Pour information, l’API collection de Kotlin fourni de nombreuses méthodes utilitaires sur les classes. |
Les companion
, vos meilleurs amis
Adrien Pessu Gautier de Saint Martin Lacaze
Un peu de théorie
Static method
Contrairement à Java, Kotlin ne supporte pas les méthodes statiques pour une classe. En fait, les méthodes statiques n’appartiennent pas à l’instance de l’objet mais plutôt au type lui-même. De ce fait, il est conseillé en Kotlin de définir les méthodes, que l’on veut statique, directement au niveau du package.
fun toUpperCase(input:String):String{
return if(input.isEmpty()) "" else input.toUpperCase()
}
La méthode précédente pourra être appelé directement (sans instance d’un objet) de la façon suivante :
fun main(args: Array<String>) {
val message = "Kotlin : le back du futur"
println("Initial case: $message")
println("Upper case: " + toUpperCase(message))
}
Puisque l’on vient de voir les méthodes statiques, on peut se demander comment déclarer un singleton. Pour rappel, le singleton est un pattern de programmation limitant l’instanciation d’une classe à une unique instance. Une fois l’instance créée, elle "vivra" tout au long de la durée de vie de votre programme.
Voici un singleton en Kotlin :
object MonSingleton {
private var count = 0
fun callMe():Unit {
println("Ça fait $count fois que je suis appelé !")
}
}
Avec l’exemple précédent, on peut appeler directement notre méthode via MonSingleton.callMe()
.
companion
Maintenant que l’on a présenté la façon de déclarer une méthode statique et un singleton, on peut se demander comment peut on faire pour avoir une méthode statique dans une classe comme en Java.
C’est à ce moment là que le companion
(ou companion object
) est utile.
Ce sera notamment utile dans le cas où l’on veut créer une factory.
class User private constructor(val login: String) {
companion object UserFactory {
fun create(login: String): User {
return User(login)
}
}
}
Comme vous pouvez le remarquer le constructeur de User est privé.
Il n’est donc pas possible d’instancier User en dehors de sa classe.
|
Le companion a également accès à toutes les méthodes ainsi que tous les membres de la classe User .
|
Il est possible de "nommer" un companion en utilisant la syntaxe suivante : companion object MonCompanion {} .
Il sera alors possible d’appeler le companion dans la classe principale via MonCompanion .
|
Pour appeler la méthode de création d’un User
, vous devez utiliser le code suivant :
User.Companion.create("myLogin")
Comme c’est un peu long et que Kotlin pense aux développeurs, vous pouvez utiliser le raccourci suivant :
User.create("myLogin")
Vous trouverez plus d’informations sur les companion
sur cette page
Exercice 3
Après autant de théorie, repassons à la pratique.
Nous allons créer notre premier companion
dans la classe EventController
.
Afin de commancer l’exercice suivant, faites un checkout du step1.
git checkout step1
Ce compagnon aura la charge à la fois de remplacer le filtre utilisé dans la méthode get
, mais également de rendre les données plus "jolie".
-
Ajouter le
companion
Letter
à la classeEventController
-
Ajouter une méthode nommée
prettier
aucompanion
-
la méthode prendra en paramètre une liste d'`Event` et retournera le type
Any
-
la méthode permettra de filter les événements sans date
-
-
Remplacer le filtre actuellement dans la méthode
get`par votre nouveau `companion
Pour vous aider, il y a des tests unitaires sur step1.
Un peu de théorie
Paramètre nommé
Contrairement à Java, Kotlin fourni le support des paramètres nommés. Ceux-ci nous permettent notamment d’être explicites sur le nommage des arguments lorsqu’ils sont passés à une fonction. Cela à l’avantage d’expliciter les appels notament lorsqu’il y a de nombreux arguments.
Dans l’exemple suivant, il n’est pas aisé de savoir à quoi correspond chaque valeur passé en paramètre.
val string = "Kotlin is a great language"
string.endsWith("Great Language", true)
Avec les paramètres nommés cela devient beaucoup plus lisible.
val string = "Kotlin is a great language"
string.endsWith(suffix = "Great Language", ignoreCase = true)
Un autre bénéfice des paramètres nommés est la possibilité pour le code appelant la fonction de réorganiser l’ordre des paramètres. Le code suivant est toujours valide.
val string = "Kotlin is a great language"
string.endsWith(ignoreCase = true, suffix = "Great Language")
Les paramètres nommés ne peuvent être utilisés que sur des fonctions définies par Kotlin et non sur des fonctions définies par Java. Cela est dû au fait que le code Java, lorsqu’il est compilé en bytecode, ne conserve pas toujours les noms des paramètres. |
Vous trouverez plus d’informations sur les paramètre nommé
sur cette page
Paramètre par défaut
Il est souvent utile de définir des paramètres par défaut pour nos méthodes. En Java, cela se traduit par l’overloading. On a alors un code au format suivant
public void myFunction(String string, boolean ignoreCase) {
// do stuff
}
public void myFunction(String string) {
myFunction(string, false);
}
Kotlin fournit une alternative très intéressante. Il est possible dans la définition d’une fonction de préciser des valeurs par défaut. L’équivalent du code Java précédent serait le suivant en Kotlin.
fun myFunction(string: String, ignoreCase: Boolean = false): Unit {
// do stuff
}
On peut alors appeler notre code de la façon suivante.
myFunction("call without default parameter", true)
myFunction("call with default parameter")
Vous trouverez plus d’informations sur les paramètres par défaut
sur cette page
Copy
Comme promis au début de ce codelab, nous allons aborder rapidement la méthode copy des Data class
.
Pour rappel, lorsque l’on déclare une Data class
, on obtient une méthode de copie prête à l’emploi.
Cette méthode vous permet de créer une nouvelle instance de votre type tout en sélectionnant les champs que vous souhaitez modifier.
Par exemple, vous pouvez décider que vous souhaitez obtenir une nouvelle instance d’une classe Event
à partir d’une instance existante dont vous souhaitez simplement modifier les champs de date et de lieu.
event.copy(location = "Tours", date = "2018-02-23")
String templates
En tant que développeurs Java, nous sommes familiers avec l’utilisation de la concaténation de chaînes de caractères. Si l’on garde la pattern appris en Java cela donnerait le code suivant en Kotlin.
val name = "TouraineTech"
print("Hello " + name)
Les String templates
sont un moyen simple et efficace d’incorporer des valeurs, des variables ou même des expressions dans une chaîne sans avoir besoin d’utiliser la concaténation précédente.
Les String templates
améliorent l’expérience du développeur lors de l’utilisation de plusieurs variables dans un seul littéral.
En effet, ils conservent la chaîne plus courte et plus lisible.
L’utilisation est extrêmement simple. Une valeur ou une variable peut être intégrée simplement par préfixer avec un symbole dollar ($):
val name = "TouraineTech"
print("hello $name")
Là où ça devient intéressant pour le templating, c’est qu’il existe plusieurs format de String
.
Pour le moment nous avons vu le String correspondant
à celui en Java avec la forme suivante val name = "TouraineTech"
.
Il existe également le raw String
.
Il se déclare de la façon suivante.
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
Par défaut le "|" est utilisé comme préfixe indiquant la position de la marge gauche du raw string .
On peut néanmois choisir un autre caractère et le passer en paramètre de la méthode trimMargin .
Par exemple, trimMargin(">") .
|
Vous trouverez plus d’informations sur les String template
sur cette page
Exercice 4
Dans l’exercice 3, nous avons créé notre companion
avec une méthode nommé prettier
.
Nous allons maintenant enrichir cette méthode pour transformer les dates du format chiffre vers le format lettres.
-
Ajouter un tableau de valeurs contenant les chaines de caractères pour les chiffres 0 à 9 en anglais
-
Pour 1, on utilisera "one"
-
…
-
-
Dans votre méthode
prettier
, pour chaque date, remplacer chaque chiffre par son équivalent en chaîne de charactères
Pour vous aider, il y a des tests unitaires sur step1.
Corrigé
Nous vous invitons à regarder notre solution pour la comparer avec la votre. Notamment la contruction de la chaîne de caractères.
Gestion de la nullité et initialisation tardive
Adrien Pessu Gautier de Saint Martin Lacaze
Un peu de théorie
La mort de l’opérateur ternaire
Bon ok, c’est peut être un peu pour le buzz.
Pour le moment, on n’a pas encore parlé des conditions. Les fameux if
.
Tout comme en Java il est possible d’utiliser le if en tant que gestionnaire du flux d’éxécution (désolé, nous n’avons pas trouvé mieux comme traduction française de flow control).
val a = 10
val b = 11
if (a < b) {
print ("Youhou a est inférieur à b")
}
// Version avec un else
if (a < b) {
print ("Youhou a est inférieur à b")
} else {
print ("Youhou b est inférieur à a")
}
Il existe une autre façon d’utiliser le if
et c’est pourquoi on peut parle plus ou mois de la fin de l’opérateur ternaire (le fameux = ? :
).
En Kotlin, if
est une expression.
Il est donc possible de s’en servir pour remplacer l’opérateur ternaire.
val max = if (a > b) a else b
On peut même imaginer plus complexe avec l’exemple suivant.
val max = if (a > b) {
print("Youhou a est le plus grand")
a
} else {
print("Youhou b est le plus grand")
b
}
Vous trouverez plus d’informations sur les String template
sur cette page
Gestion du null
Le système de type de Kotlin vise à éliminer le danger des références nulles à partir du code. Ce problème est également connu sous le nom de The Billion Dollar Mistake. Bien entendu on parle de la fameuse NullPointerException de Java (aka NPE).
Pour régler ce problème, Kotlin ne permet pas par défaut d’avoir des références nulles. Le code suivant est donc invalide.
var kotlin: String = "Kotlin for te win !"
kotlin = null // Le code ne compile pas
Il est possible de déclarer une variable nulle. vDans ce cas nous utiliserons une précision syntaxique pour indiquer que l’on autorise le null.
var java: String? = "Java for the NPE"
java = null // Dans ce cas le code compile
Notez bien l’utilisation du ? dans la déclaration de la variable java .
|
A part la déclaration explicite d’une variable nulle, il existe d’autres cas pouvant générer une NPE :
-
un appel à
throw NullPointerException()
-
l’utilisation de l’opérateur !! (non présenté dans cette partie cf. le site officiel de Kotlin)
-
un problème de données inconsistantes lors de l’initialisation
-
l’intéraction avec du code Java
Alors qu’est ce qui change entre nos deux morceaux de code précédent.
Pour le premier, c’est simple l’appel suivant sera valide.
val length = kotlin.length
Pour le second, ce n’est pas le cas.
val length = java.length // Erreur de compilation
Le compilateur va nous forcer à gérer les cas de la nullité.
Et clairement c’est un gros progrès par rapport au compilateur Java.
Pour la gestion de la nullité, on peut alors utiliser le if else
traditionnel, ou bien le safe call operator
?.
.
val length: Int? = java?.length // Ici ça compile
La valeur de length
sera soit la taille de java
si java
est valorisé, ou alors null
.
On peut remarquer que le type de length sera alors Int? et non pas Int
|
Là où cela devient très intéressant c’est que l’on peut chaîner les opérateurs ?.
.
val profileLabel = user?.profile?.label
Dans le cas précédent, profileLabel
sera null
si user
est null
ou si profile
est null
.
Vous trouverez plus d’informations sur les String template
sur cette page
Exercice 5
Afin de commancer l’exercice suivant, faites un checkout du step2.
git checkout step2
Reprenons notre projet et enrichissons un peu notre code. Nous allons maintenant permettre à notre endpoint d’API de gérer deux cas d’affichage. Le premier sera l’affichage sans transformation de la date des événements, le second sera celui que l’on a mis en place dans l’exercice précédent.
-
Ajouter un paramètre optionnel dans la méthode get de
EventController
-
En fonction du paramètre d’entrée, utiliser ou non le
companion
-
on vous laisse choisir les valeurs permettant ou non d’utiliser le
companion
-
Pour vous aider, il y a des tests unitaires sur step2.
Un peu de théorie
Depuis la version 1.2 de Kotlin il est possible d’initialiser plus tard une val
dans le code.
Pour cela il faut utiliser le modifier lateinit
.
Cela est notamment utile lors de l’utilisation d’une lambda dans un constructeur.
// A cycle of three nodes:
lateinit var third: Node<Int>
val second = Node(2, next = { third })
val first = Node(1, next = { second })
third = Node(3, next = { first })
Pour éviter les problèmes lors de l’utilisation des variables déclarée avec le modifier lateinit
, il est possible d’utiliser la méthode isInitialized
println("isInitialized before assignment: " + this::lateinitVar.isInitialized)
lateinitVar = "value"
println("isInitialized after assignment: " + this::lateinitVar.isInitialized)
Le modifier lateinit est régulièrement utilisé dans les projets Spring.
L’injection de dépendance pourra toujours être effectuée sans problème et le code compilera.
|
Vous trouverez plus d’informations sur le lateinit
sur cette page.
Exercice 6
Ici nous n’allons pas coder, mais simplement découvrir l’utilisation du lateinit
dans le cadre d’un projet Spring.
Regarder le code de la classe EventController
.
Flow control
Adrien Pessu Gautier de Saint Martin Lacaze
Une peu de théorie
Pour le moment nous avons vu les rapidement les éléments suivants de Kotlin :
-
val
etvar
-
class
etdata class
-
méthode statique
-
companion
-
paramètre nommé
-
paramètre par défaut
-
copy
-
string templates
-
if / else
-
gestion de la nullité avec l’opérateur Elvis
Nous allons passer maintenant à deux autres éléments nécessaire pour écrire la majorité des algorithmes d’un programme.
range
Les range
sont des expressions permettant de créer rapidement une plage de valeur très rapidement.
Pour les utiliser, on peut utiliser la méthode rangeTo
.
1.rangeTo(10)
Il existe une écrire plus courte via l’opérateur ..
.
1..10
Il est possible d’utiliser également les range sur des lettres.
Par exemple val aToZ = "a".."z"
|
On peut tester la présence d’un élément dans une plage de valeur de la manière suivante.
if (i in 1..10) { // équivalent à 1 <= i && i <= 10. Avouez que c'est plus agréable à lire non ?
println(i)
}
Vous trouverez plus d’informations sur les range
sur cette page.
When
L’expression when
est un équivalent du switch
présent dans de nombreux langages de programmation.
C’est également une expression fonctionnelle permettant de faire du pattern matching.
Il existe deux façon d’utiliser le when
.
Soit il prend un argument.
Soit on ne met pas d’argument.
Dans ce second cas, le when permet de remplacer facilement une série de if / else.
L’une des écritures les plus simple du when
est la suivante :
fun whatNumber(x: Int) {
when (x) {
0 -> println("x is zero")
1 -> println("x is 1")
else -> {
println("X is neither 0 or 1")
}
}
}
Dans ce premier cas, on prend un paramètre en entrée.
Le when permet alors de vérifier si l’on "matche" une des possibilités du when .
|
L’autre façon d’écrire le when est de ne pas passer d’arguments. Dans ce second cas, la syntaxe est la suivante.
fun whichOneIsBigger(x: Int, y: Int) {
when {
x < y -> println("x is less than y")
x > y -> println("X is greater than y")
else -> println("X must equal y")
}
}
A plusieurs moment, on a indiqué que when
était une expression.
Celle-ci est donc assignable à une variable.
La syntaxe suivante est valide.
fun startsWith(any: Any, prefix: String): Boolean {
val result = when (any) {
is String -> any.startsWith(prefix)
else -> false
}
return result
}
On peut même simplifier le code précédent.
fun startsWith(any: Any, prefix: String): Boolean {
return when (any) {
is String -> any.startsWith(prefix)
else -> false
}
}
Vous trouverez plus d’informations sur l’expression when
sur cette page.
Exercice 7
git checkout step3
Dans l’exercice 4, nous avons remplacé chaque chiffre de la date par son équivalent en chaîne de charactères. Comme vous pouvez le remarquer ce n’est pas comme cela que l’on doit lire une date.
Nous allons donc modifier notre companion
pour gérer les cas des milliers, des centaines et des dizaines.
Pour vous aider, il y a des tests unitaires sur step3.
Pour aller plus loin
Adrien Pessu Gautier de Saint Martin Lacaze
Exercice 8
Isoler la partir conversion de chiffre en lettre et déclarer ces instructions dans une fonction en tant qu’Extension de la classe String.
Exercice 9
Passer la fonction de conversion de chiffre en lettre en tant que paramètre de la fonction de la coroutine.
Smart casts (pas besoin de recaster si tu fais un is) Interface function (single expression + member function) function literals