Open Source Your Knowledge, Become a Contributor
Technology knowledge has to be shared and made accessible for free. Join the movement.
Classes et Objets
Déclaration et instanciation
Les méthodes et attributs sont par défaut public. Des getters (val / var) et setters (var) sont
générés pour les attributs.
Constructeurs
Les paramètres du constructeur primaire viennent juste après le nom de la classe. Les paramètres taggés val ou var seront des attributs de la classe.
L'implémentation du constructeur primaire, souvent optionnel, se fait dans un bloc init { … }
Les constructeurs secondaires sont définis avec les blocs constructor() { … }
Accesseurs personnalisés
Si nécessaire, nous pouvons personnaliser les getters et les setters habituellement générés automatiquement. Remarquez la
variable réservée field !
Héritage
Les classes sont final par défaut. Pour qu'une classe hérite d'une classe parente, cette dernière doit être taggée open
Data classes
En plus des accesseurs, Kotlin peut générer les méthodes equals, hashcode, toString et clone, bien pratiques pour
les classes de modèle ou data classes. C'est une fonctionnalité très similaire à l'annotation @Data de Lombok.
Attention: les data classes fonctionnent mal avec l'héritage,
Attention : Les attributs pris en compte dans ces méthodes sont ceux déclarés dans le constructeur par défaut.
C'est une manière d'ignorer certains champs (dans le cas de boucle par exemple A <--> B).
Interface
Comme en Java, les interfaces sont des prototype de classe sans implémentation.
interface IDisplay{
fun getSize(): Pair<Int,Int>
}
class Display:IDisplay,Serializable{
override fun getSize(): Pair<Int, Int> {
TODO("not implemented")
}
}
Object et Singleton
Kotlin supporte nativement (et thread-safe) la création d'une instance unique d'une classe avec le mot-clé object.
On utilise aussi object pour créer des classes anonymes.
Fonctions d'extension
Les fonctions d'extensions sont une notion important en Kotlin : elles permettent d'étendre facilement les fonctionnalités d'une classe à l'extérieur de celle-ci.
Au sein de cette fonction d'extension, nous avons accès aux attributs membres de la classe en passant par la variable this
Companion objects
La notion de static en Java est remplacée par la notion de companion object en Kotlin. Pour rendre statiques des attributs
ou des méthodes, on les encapsule dans un object singleton associé à la classe
Les companion objects sont plus intéressants d'un point de vue du design d'application, car ils peuvent étendre et implémenter d'autres classes/interfaces. On peut aussi associer une fonction d'extension aux companion objects :
Cependant, depuis Java (voir la décompilation de la classe), l'appel devra se faire ainsi HasStaticMembers.Companion.doSomething().
Pour les utilisateurs Java de votre librairie, ajoutez l'annotation @JvmStatic à vos membres HasStaticMembers.doSomething()
Exercice : Comparer les deux versions Java générées avec ou sans @JvmStatic
Fonctions infix
Les méthodes ou les fonctions d'extension à un seul paramètre strictement et taggées infix, peuvent être appelées avec une notation spéciales : sans point ni paranthèses !
De nombreuses fonction d'extension existent dans le language et dans les nombreuses librairies Kotlin. to est probablement la plus connue et utilisée.
Elle permet de créer un objet Pair (de la bibliothèque Kotlin), un tuple associant 2 valeurs.
Surcharge d'opérateurs et conventions
Avec le mot clé operator, nous pouvons surcharger les opérateurs usuels pour les utiliser avec nos propres classes,
en implémentant les méthodes suivantes :
opérateurs unaires
inc:a++dec:a--
opérateurs binaires
plus:a + bminus:a - btimes:a * bdiv:a / brem:a % brangeTo:a..bcontains:a in b
exemple :
exemple : LocalDate + Period
Lamda avec récepteur
Le principe de base est une fonction d'extension passée en paramètre d'une méthode. En schématisant, la signature ressemble alors à ça
Receiver.(In) -> Out.
Avec cette fonctionnalité, on s'approche de la puissance du language Kotlin pour la réalisation de DSL (Domain Spécific Language).
La lamdba avec récepteur la plus connue, fournie par le language, est apply avec l'implémentation suivante :
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
- C'est une fonction d'extension générique qui prend en paramètre une fonction d'extension sur le même receiver
T - Elle est disponible pour tous les types d'objet
- Son implémentation exécute juste la lambda fournie.
- Elle retourne l'objet récepteur
Scoped functions
Kotlin fournit quelques fonctions d'extensions à toutes les objets, très pratiques en facilitant l'écriture et permettant l'enchainement de fonctions :
apply (vue précédemment), let, run, also et with.
Ces fonctions apportent un objet arbitraire dans un nouveau contexte. Cet objet de contexte est accesible par it (ou autre nom défini) ou par this.
Sur le principe, ces scoped functions se ressemblent beaucoup malgré leurs noms déroutants , avec toutefois quelques subtilités …
let
public inline fun <T, R> T.let(block: (T) -> R): R
letapplique un traitement (lambda) sur un objetTet retourne un résultat de transformationRletremplace avantageusement les tests idiomatiquesif(object == null)
val mapped = value?.let { transform(it) } ?: defaultValue
run
inline fun <T, R> T.run(block: T.() -> R): R
runest sensiblement identique àletà l'exception du paramètre lamdba avec récepteur. L'accès à l'objet contexte parthis.
also
inline fun <T> T.also(block: (T) -> Unit): T
alsopermet d'appliquer un traitement sur un objetT. À la différence deletetrun,alsoretourne forcément l'objet cible.- il est très pratique par exemple pour l'initialisation d'objets sans passer par des builders
Par exemple :
fun buildWorkspace():Workspace {
val workspace = Workspace()
workspace.name = "Custom Workspace"
workspace.init()
return workspace
}
devient :
fun buildWorkspace() = Workspace()
.also {
it.name = "Custom Workspace"
it.init()
}
apply
inline fun <T> T.apply(block: T.() -> Unit): T
applyest commealso, mais avec une lambda avec récepteur, ce qui veut dire que l'objet cible est accessible parthis
exemple :
fun buildWorkspace() = Workspace()
.apply {
name = "Custom Workspace"
init()
}
with
inline fun <T, R> with(receiver: T, block: T.() -> R): R
withcontrairement aux autres, n'est pas une fonction d'extensionwithpermet de choisir le contextthiset d'isoler une série de variables dans un bloc de code
Type aliases
Avec la généricité, arrivent les types de longueur kilométrique. Le mot clé typealias permet de simplifier et définir un alias à des types trop complexes.
typealias SpecialList<T> = MutableList<Map<String, List<T>>>
val myList:SpecialList<Long> = mutableListOf()
myList.add(mapOf("Fibonacci" to listOf(1L,3L,5L,8L)))
Quizz
Question 1
class Parent{
init {
println("Hello from Parent")
}
}
class Child:Parent(){
init {
println("Hello from Child")
}
}
Exercices
unwrap
Écrire la fonction d'extension unwrap qui convertit un Optional en type nullable
Startrek
Créer le modèle métier suivant :
Construire un Univers avec le vaisseau Enterprise et faites le déplacer de la planète Terre vers la planète Vulcain.
Logger cette information lors du déplacement (println).