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 + b
minus
:a - b
times
:a * b
div
:a / b
rem
:a % b
rangeTo
:a..b
contains
: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
let
applique un traitement (lambda) sur un objetT
et retourne un résultat de transformationR
let
remplace avantageusement les tests idiomatiquesif(object == null)
val mapped = value?.let { transform(it) } ?: defaultValue
run
inline fun <T, R> T.run(block: T.() -> R): R
run
est 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
also
permet d'appliquer un traitement sur un objetT
. À la différence delet
etrun
,also
retourne 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
apply
est 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
with
contrairement aux autres, n'est pas une fonction d'extensionwith
permet de choisir le contextthis
et 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
).