Premier pas en Kotlin
Histoire de Kotlin
- created by JetBrain 2010
- concis, sur, pragmatique et 100% interopérable avec Java
- Kotlin peut être utilisé dans la création d’application mobile native sur Android, mais également dans la création d’application web (J2EE)
Les classes java et kotlin compilé par le compilateur sont identique et donc lisible par la jvm
statiquement typé : chaque variable d’un programme est connu au moment de la compilation.
val zero = 0
val message = "Hellow world !"
fun sayHello() = "Hello everyone !"
C'est le compilateur Kotlin, qui va "déduire" automatiquement le type de vos variables. On appelle cela, "l’inférence de type" (ou "type inference" en anglais)
Pour résumer, un langage "statiquement typé" comme Kotlin (ou Java) présente plusieurs avantages majeurs, comme :
- Performance : Comme les types sont connus au moment de la compilation, pas besoin de les "déduire" au moment de l’exécution du programme.
- Fiabilité : Le compilateur vous indique beaucoup plus d’erreurs au moment de la compilation, minimisant les chances que votre application crashe durant son exécution.
- Efficacité : Un langage statiquement typé permet de réaliser beaucoup plus facilement des "refactoring" et permet également la mise en place d’outils performants au sein de l’IDE comme "l’autocomplétion".
aussi un langage dit "fonctionnel".
- First-class function : fonctions en tant que valeurs. Nous pourrons par exemple, stocker des fonctions dans des variables, passer en paramètres des fonctions, ou encore retourner des fonctions ! des lambdas
- Immuabilité : L’approche fonctionnelle encourage l’utilisation de variables dites "immuables", c’est-à-dire que leur état ne pourra pas changer après leur création
Cette technique permet d’améliorer la fiabilité et les performances d’un programme, en le rendant notamment résistant aux problèmes liés au multi-threading
- Aucun effet de bord :) En utilisant uniquement des fonctions dites “pures”, on s’assure que le résultat retourné sera toujours le même.
Déclarez et initialisez des variables
- val sera obligatoirement immuable
- Vous devez initialiser ce genre de variable dans le même bloc de code que là où vous l’avez déclarée
Exercice
private lateinit var answer: String
private val question = "What is your name ?"
fun main(args: Array<String>) {
println(question)
answer = "Leo"
println("Your name is $answer")
}
Implementez differentes fonctions
En Kotlin, if est une expression et non pas une instruction comme en Java. D’ailleurs, en Java, toutes les structures de contrôles sont des instructions !
une expression est toute partie de votre code qui peut retourner une valeur. Ainsi, 1+1 est une expression, maxOf(5, 7) est une expression.
une instruction est un bloc de code qui ne retourne aucune valeur. Par exemple, var username = "Phil" est une instruction
Exercice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println(getResult(1,2))
println(getUsernameUpperCase("Sam"))
println(isUsernameOfTeacher("Phil"))
println(isUsernameOfTeacher("Ambroise"))
println(isUsernameOfTeacher("Andre"))
}
fun getResult(a:Int, b:Int) = a + b
fun getUsernameUpperCase(username:String) = username.toUpperCase()
fun isUsernameOfTeacher(username:String) = if(username == "Phil" || username == "Ambroise") true else false
fun isUsernameOfTeacher(username: String) = username == "Phil" || username == "Ambroise"
Generez vos premiere classes
- private : Un membre déclaré comme private sera visible uniquement dans la classe où il est déclaré.
- protected : Un membre déclaré comme protected sera visible uniquement dans la classe où il est déclaré ET dans ses sous-classes (via l’héritage).
- internal : Un membre déclaré comme internal sera visible par tous ceux du même module. Un module est un ensemble de fichiers compilés ensemble (comme une librairie Gradle ou Maven, par exemple).
- public : Un membre déclaré comme public sera visible partout et par tout le monde.
Exercice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
val kotlinForTroll = Course("KT0", "Kotlin for troll - Be a better troll.", 10, false)
println("Check out my new course : ${kotlinForTroll.title}")
}
class Course(private val id: String, var title: String, val duration: Int, var isActive: Boolean)
Decouvrez les structures de controle en Kotlin
Gerez des choix et des conditions
Exercice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("${mult(8,10)}")
val c = Color.RED
writeName(c)
}
enum class Color(val label:String){
BLUE(label="BLUE"),
RED(label="RED"),
YELLOW(label="YELLOW")
}
fun writeName(c:Color) = println(c.label)
fun mult(a:Int, b:Int) =
if(a<b){
a*10
}else {
b*10
}
- --
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("Min of those two numbers : ${minOf(10, 11)}")
println("Red color to string: ${colorToString(Color.RED)}")
}
// --- FUNCTIONS ---
private fun minOf(a: Int, b: Int) = (if (a < b) a else b) * 10
private fun colorToString(color: Color) = when (color) {
Color.RED -> "RED"
Color.BLUE -> "BLUE"
Color.GREEN -> "GREEN"
}
// --- ENUM ---
private enum class Color { RED, BLUE, GREEN }
Iterez grace aux boucles
La boucle for s’utilise désormais sous la forme in
Nous allons ainsi pouvoir appeler les méthodes génériques suivantes :
- listOf: Permet de créer une liste d’éléments ordonnée et immuable.
- mutableListOf: Permet de créer une liste d’éléments ordonnée et muable.
- setOf: Permet de créer une liste d’éléments désordonnée et immuable.
- mutableSetOf: Permet de créer une liste d’éléments désordonnée et muable.
Euh, quelle est la différence entre list et set, déjà ?
Une liste de type list contiendra des éléments uniques et/ou différents de manière ordonnée. Ses éléments seront accessibles grâce à un index. À l’inverse, une liste de type set contiendra des éléments uniques et distincts de manière non ordonnée. Ses éléments ne pourront pas être accessibles via un index.
Exercice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
while20Items()
val t = listOf("a", "b", "c")
showArrayContent(t)
}
// FUNCTION
fun while20Items() {
var i = 20
while(i>0){
i--
println(i)
}
}
fun showArrayContent(t:Collection<String>){
for(e in t) {
println(e)
}
}
- --
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
while20Items()
println("-------------------------------")
showArrayContent(arrayOf("K", "O", "T", "L", "I", "N"))
}
// ---
private fun while20Items(){
var i = 0
while (i < 20){
println("$i")
i++
}
}
private fun showArrayContent(strings: Array<String>){
for (string in strings) print(string)
}
Decouvrez le Smart Cast
en Kotlin, le cast est implicite avec le mot-clé is : c’est ce que l’on appelle le Smart Cast
Nous convertissons ici le contenu de la variable anyObject en une variable de type String grâce au mot-clé as : ce type de conversion est appelée "Conversion non sécurisée" (ou "Unsafe cast" en anglais).
Exerice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
guessTheType('c')
guessTheType("c")
guessTheType(1)
guessTheType(true)
guessTheType(listOf("a","b","c"))
guessTheType(arrayOf("a","b","c"))
}
fun guessTheType(o:Any) = when(o){
is Int->println("int $o")
is String->println("string $o")
is List<*>->println("list $o")
is Boolean->println("bool $o")
is Array<*>->println("array $o")
else->println("other $o")
}
//---
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
guessTheType(10)
guessTheType("Troll")
guessTheType(arrayOf(1, 2))
guessTheType(listOf(1, 2))
guessTheType(false)
guessTheType(9.0) // It's a Double...
}
private fun guessTheType(any: Any) = when (any){
is Int -> println("It's an Integer !")
is String -> println("It's a String !")
is Array<*> -> println("It's an Array !")
is List<*> -> println("It's a List !")
is Boolean -> println("It's a Boolean !")
else -> println("Error ! Type not recognized...")
}
Maitrisez les exceptions
en Kotlin, le try/catch ainsi que le throw sont des expressions !
On notera que les propriétés de la classe User sont toutes susceptibles de contenir une valeur nulle
Cette fonction ne retournant rien du tout, si ce n’est une exception, son type de retour sera alors Nothing .
Exercice
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
try{
println("-1 = ${isUserOld(-1)}")
}catch(e: Exception){
println(e.message)
}
try{
println("101 = ${isUserOld(101)}")
}catch(e: Exception){
println(e.message)
}
println("true = ${isUserOld(70)}")
println("false = ${isUserOld(40)}")
}
fun isUserOld(age:Int):Boolean{
if(age<0)throw IllegalArgumentException("too young")
if(age>100)throw IllegalArgumentException("too old")
if(age<65) return true
else return false
}
//---
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("Is user is old ? ${isUserOld(64)}")
println("Is user is old ? ${isUserOld(67)}")
println("Is user is old ? ${isUserOld(-2)}")
println("Is user is old ? ${isUserOld(102)}")
}
// ---
private fun isUserOld(age: Int) = when {
age > 100 -> throw Exception("too old !")
age > 65 -> true
age < 0 -> throw Exception("too young !")
else -> false
}
Maitrisez la puissance de Koltin
Ameliorer vos fonctions
Compatibilité avec Java : Les valeurs par défaut de paramètres n’étant pas compatibles avec Java, vous devrez ajouter l’annotation @JvmOverloads devant la méthode souhaitée afin que le compilateur puisse générer automatiquement les méthodes de surcharges pour Java.
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.
Decouvrez les extensions
Une extension se déclare dans un fichier à part(1), que l’on nomme généralement d’après le nom de la classe que l’on souhaite étendre, suivi du mot "Extensions" ou "Ext".
Sachez également qu’une extension ne pourra pas être surchargée : impossible donc d’utiliser le mot-clé override pour modifier le comportement d’une extension déjà créée.
Exercice
import stringExt.toPlural
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
var word:String = "table"
println("${word.toPlural()}")
}
package stringExt
fun String.toPlural() = this + "s"
Enrichissez vos classes
toutes les classes et méthodes sont "fermées" en Kotlin
D’ailleurs, voici un petit récapitulatif des modificateurs d’accès disponibles en Kotlin (à ne pas confondre avec les modificateurs de visibilité vus dans un précédent chapitre !) :
- final : Classe/Méthode/Propriété ne pouvant pas être redéfinie. C’est l’état par défaut de tous les éléments en Kotlin.
- open : Classe/Méthode/Propriété pouvant être redéfinie. Ce modificateur d’accès doit être indiqué explicitement.
- abstract : Classe/Méthode/Propriété devant être redéfinie. Ce modificateur d’accès peut être utilisé uniquement dans des classes abstraites.
Et bien entendu, le mot-clé override sera utilisé pour redéfinir un élément d’une classe parente (ou d’une interface).
- Afficher son contenu sous forme de texte via la méthode toString() (dans une console de log par exemple)
- Comparer deux objets de même type (via la méthode equals() de sa superclasse)
- Dupliquer un objet de manière distincte en un second objet (via la méthode copy() ou clone() de sa superclasse)
En Java, cela nous obligeait à redéfinir les méthodes toString() , hashCode() , equals() et copy() dans le corps de notre classe
le mot-clé static n’existe plus
Singleton
Sachez également que le Singleton créé grâce au simple mot-clé object sera naturellement "thread safe"... Pas besoin de gérer ce cas si particulier dans notre code !
Exercice
open class Button(var title:String="init", var bgColor:String="#FFF") {
}
class CircleButton(title:String="cb", bgColor:String="#FFF", var radius:Int=10):Button(title,bgColor){
}
data class User(var email:String, var password:String,var urlImage:String?){
companion object {
fun newInstance(email:String, password:String) = User(email, password, null)
}
}
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
var u1 = User("user1", "password1", "image1")
println("u1.email = ${u1.email}")
u1.email = "emailChanged"
println(u1)
var u2 = User.Companion.newInstance("user2", "password2")
println(u2)
var b = Button()
b = Button("title")
b = Button(bgColor="color")
b = Button("title2","color2")
var cb = CircleButton()
cb = CircleButton("title")
cb = CircleButton(bgColor="color")
cb = CircleButton(radius=20)
cb = CircleButton("title2","color2",30)
}
// ---
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
val user = User("toto@gmail.com", "azerty", "https://url.com")
println(user)
val button = Button()
val circleButton = CircleButton()
}
// ---
data class User (var email: String,
var password: String,
var urlImage: String){
companion object {
fun newInstance(email: String, password: String, urlImage: String) = User(email, password, urlImage)
}
}
// ---
open class Button(var title: String = "button",
var bgColor: String = "#FFF")
class CircleButton(_title: String = "circleButton",
_bgColor: String = "#FFF",
var radius: Float = 1.0f): Button(_title, _bgColor)
Tirez parti des lambdas
Maintenant, nous allons créer une fonction pouvant contenir en paramètre une autre fonction : on appelle ce genre de fonction les "fonctions d’ordre supérieur" (ou "Higher-Order Functions", en anglais).
Le mot-clé inline va permettre d’indiquer à Kotlin de ne pas créer d’objet Function représentant la lambda, mais plutôt de déplacer le code de celle-ci directement là où elle sera appelée
Ainsi, de manière générale, si vous décidez de créer une "fonction d’ordre supérieur" dans votre programme, pensez toujours à rajouter le mot-clé inline pour optimiser la mémoire de votre programme.
Exercice
inline fun List<User>.maxUser(selector: (User) -> Int): User {
var maxUser = this.first()
var maxValue = selector(maxUser)
var possibleMaxValue: Int
for (user in this) {
possibleMaxValue = selector(user)
if (possibleMaxValue > maxValue) {
maxValue = possibleMaxValue
maxUser = user
}
}
return maxUser
}
fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
val users: List<User> = listOf(
User("toto@gmail.com", 20, 2000),
User("hello@gmail.com", 18, 0),
User("oc@gmail.com", 35, 1000))
println(users.maxUser { it.experience })
println(users.maxUser { it.age })
}
Entraînez-vous à développez une API en Kotlin
créer une application web en Kotlin : Ktor
Ainsi, votre mission est de créer une application web, OCWebServer, qui devra :
- Fonctionner en local et répondre à l'adresse http://127.0.0.1:8080/
- Implémenter la navigation suivante :
- / : Afficher le message : "Welcome to OpenClassrooms brand new server !"
- /course/top : Afficher une réponse Json correspondant au meilleur cours sorti par OpenClassrooms
- /course/{id} : Afficher les informations d'un cours. Pour la démo, vous devrez gérer uniquement les cours possédant l'identifiant 1, 2 et 3. Vous renverrez un message d'erreur si d'autres identifiants sont renseignés.
Attention, toutefois, vous devrez respecter les règles suivantes dans votre code :
- Utilisez au moins une fois une extension.
- Créer au moins une classe data.