Formation Kotlin

Takoyaki
37.8K views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

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

Documentation

public inline fun <T, R> T.let(block: (T) -> R): R
  • let applique un traitement (lambda) sur un objet T et retourne un résultat de transformation R
  • let remplace avantageusement les tests idiomatiques if(object == null)
val mapped = value?.let { transform(it) } ?: defaultValue

run

Documentation

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 par this.

also

Documentation

inline fun <T> T.also(block: (T) -> Unit): T
  • also permet d'appliquer un traitement sur un objet T. À la différence de let et run, 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

Documentation

inline fun <T> T.apply(block: T.() -> Unit): T
  • apply est comme also, mais avec une lambda avec récepteur, ce qui veut dire que l'objet cible est accessible par this

exemple :

fun buildWorkspace() = Workspace()
    .apply { 
        name = "Custom Workspace"
        init() 
    }

with

Documentation

inline fun <T, R> with(receiver: T, block: T.() -> R): R
  • with contrairement aux autres, n'est pas une fonction d'extension
  • with permet de choisir le context this 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")
    }
}
Qu'affiche la création de 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 :

startrek

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).

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content