📚 Fonctionnalités du langage Kotlin

Kotlin est un langage qui support les paradigmes orienté objet et fonctionnel. Ce chapitre couvre les caractéristiques basiques et intermédiaires. Le chapitre suivant couvrira les fonctionnalités avancées.

Caractéristiques de base

Constructions de base (variables, flux de contrôle)

  • Kotlin est typé statiquement et prend en charge le typage implicite.
    • Typage statique : les types ne peuvent pas changer à l'exécution (c'est l'inverse du typage dynamique).
    • Typage implicite : le compilateur peut inférer le type tant que c'est possible.
  • var crée des variables ré-assignables.
  • val crée des variables qu'on ne peut plus ré-assigner.
  • Les points-virgules sont facultatifs.
  • Kotlin prend en charge la déclaration de haut niveau des variables et des fonctions (elles peuvent être déclarées en dehors des classes).
  • L'interpolation de chaîne de caractères est disponible avec cette syntaxe ${expression}.
  • if et when sont des expressions (elles peuvent renvoyer une valeur).
    • when est équivalent à l'instruction switch des autres langages
    • L'opérateur ternaire n'est pas disponible. L'expression if le remplace.
  • for-each est le seul type de boucle for disponible.
  • La programmation orientée objet est prise en charge comme en Java avec quelques fonctionnalités supplémentaires telles que les extensions.
  • Le compilateur prend en charge la Null Safety. Il permet d'écrire du code sans erreur de pointeur nulle vérifié à la compilation.
  • La programmation fonctionnelle est prise en charge (fonctions d'ordre supérieur et fonctions en tant qu'éléments de 1ère classe, etc.).

Utiliser val par défaut

Utiliser var uniquement si vous réassignez une variable ou argument.

▶️ this codeopen in new window highlights the above features.

Les fonctions

Dans cette section, les termes 'argument' et 'paramètre' sont utilisés de manière interchangeable.

Les fonctions de Kotlin ont les caractéristiques suivantes :

  • Déclaration : fun functionName(arg1 : type1 = defaultvalue1, ...) : retunrType.
  • Appeler une fonction en passant la valeur dans l'ordre de déclaration.
    • Utilisez des étiquettes d'argument pour plus de clarté, cependant, cela permet également un classement arbitraire des arguments.
  • Les arguments optionnels ont une valeur par défaut et peuvent être omis lors de l'appel.
  • Les fonctions sont des éléments de première classe ou citoyens : elles peuvent être affectées à une variable, passées en tant que paramètre de fonction ou renvoyées par une fonction.
    • 💡 Une fonction qui prend une fonction comme argument ou en renvoie une est une fonction d'ordre supérieur.
  • Un type de fonction peut être exprimé comme suit : (typeOfParam1, typeOfParam2, etc) -> returnType (Le type de retour vide est Unit).
  • Les fonctions anonymes utilisent la syntaxe suivante { argName1, argName2, etc. -> // code }
    • Aussi appelées fonctions lambda ou fonctions littérales
  • Le dernier argument de la fonction peut être mis après la fermeture après la parenthèse fermante compute(9, 5) { x, y -> x * y }

▶️ Ce codeopen in new window illustre les fonctions en Kotlin.

La prochaine section abordera le null safety.

Null safety

null safety est une fonctionnalité du compilateur qui élimine la fameuse Null pointer exception ou npe. En effet, le compilateur signale des erreurs et des avertissements lorsque nous manipulons des types nullables (également appelées types optionnels) dès qu'il y a un risque de npe à l'exécution. Ainsi, afin de mettre Voici une liste des fonctionnalités de sécurité null fournies par Kotlin :

  • Tous les types ne sont pas nullables par défaut ; nous ne pouvons pas affecter null à une variable ou à un argument.
    • Par exemple, ce code échoue var s: String = null.
  • Un type peut être rendu nullable en le suffixant avec un ?. Par exemple : var s : chaîne ? = nul.
  • Kotlin interdit d'appeler une méthode ou une propriété de type non nullable, sauf si l'on fait l'une de ces possibilités :
    • Utilisez le chaînage optionnel avec le suffixe ?.
    • Fournissez une valeur par défaut avec l'opérateur elvis ?:.
    • Smart-cast le nullable dans un non-nullable.
    • Utilisez l'opérateur !! qui élimine les vérifications du compilateur. Cela ne devrait jamais être utilisé.

Ne jamais déballer avec !!

Car cela équivaut à désactiver la null safety. Utilisez les autres possibilités à la place.

▶️ ce codeopen in new window illustrate la null safety et les types optionnels.

La classe `Optional` de Java ne fournit aucun protection à la compilation

Ce code lance une npe en Java: Optional<String> s = null; s.isPresent();. Le compilateur Java (au moins à la version version 17) ne propose pas d'équivalent à ce que propose Kotlin comme le smart casting.

Énumérations

Les énumérations permettent de travailler avec un groupe de valeurs de façon cadrée. Contrairement aux énumérations Java, les énumérations Kotlin sont des classesopen in new window. Les enum class de Kotlin fournissent ces fonctionnalités :

  • Les expressions when prennent en charge les énumérations.
  • Une enum class peut définir des méthodes et implémenter des interfaces mais elle ne peut pas dériver d'une classe.
  • Il existe des méthodes pour lister les constantes d'une enum class.
  • Chaque constante d'une énumération a des propriétés pour obtenir son nom et sa position (en commençant par 0).

▶️ ce codeopen in new window illustres les enum en Kotlin de façon succincte. Veuillez consulter la documentation officielle pour aller plus loinopen in new window.

Exercices

Exercice 1

Veuillez cliquer sur le lien pour consulter l'énoncéopen in new window

Déplier pour consulter la solution

Solutionopen in new window

Exercise 2

Veuillez cliquer sur le lien pour consulter l'énoncéopen in new window

Déplier pour consulter la solution

Solutionopen in new window

Fonctionnalités intermédiaires

Programmation orientée objet

Kotlin permet d'écrire du code Orienté Object concis grâce aux caractéristiques suivantes :

  • Concepts disponibles : classes, héritage, interfaces et classes abstraites.
  • Prise en charge possée des propriétés : les getters et les setters sont automatiquement implémentés.
    • On peut les personnaliser les accesseurs en définissant les fonctions get() et set(value) à côté de la déclaration de la propriété.
  • Les arguments du constructeur sont définis à côté du nom de la classe class ClassName(arg1, atg2, )
  • Préfixer les arguments d'un constructeur avec val ou var en fait une propriété (val la rend non ré-assignable).
  • Le nom du constructeur est init et ne nécessite pas de paramètres.
  • Le compilateur vérifie que toutes les propriétés non nullables sont initialisées à la fin du constructeur.
    • ⚠️ Le compilateur ne vérifie pas l'initialisation des propriétés lateinit. Ainsi, y accéder avant alors qu'elles ne sont pas initialisés provoque une exception.
  • Une classe doit être préfixée avec open pour permettre l'héritage.
  • Kotlin utilise le niveau d'accès public par défaut.
  • L'opérateur d'égalité == appelle implicitement la méthode equals() (contrairement à Java qui utilise l'égalité de référence).
  • Un objet compagnon contient des méthodes et des propriétés statiques.
  • Les extensions ajoutent des fonctions et des propriétés aux classes existantes.
    • 💡 Ils remplacent l'héritage dans de nombreuses situations.
    • Par exemple, nous pouvons ajouter des fonctions à la classe String au lieu de créer une nouvelle classe StringUtils.
  • Les classes et interfaces scellées ne peuvent pas être étendues ou implémentées par des tiers.

Ne définir les accesseurs que si vous avez un comportement personnalisé

Kotlin prend en charge les propriétés de façon plus poussée que Java et permet d'ajouter des accesseurs ultérieurement sans refactoriser le code qui appelle ces propriétés. Ainsi, par défaut, il suffit de définir le nom des propriétés sans accesseurs et on peut les utiliser directement.

▶️ ce codeopen in new window illustre la POO en Kotlin.

Data class

Ce sont des classes qui implémentent des méthodes communesopen in new window:

  • equals(), hashCode(), copy() et toString()
  • componentN() qui est une syntaxe alternative pour récupérer les propriétés.

Cependant, les data class ont des restrictionsopen in new window:

  • Le constructeur principal doit avoir au moins un paramètre.
  • Tous les paramètres du constructeur principal doivent être marqués comme val ou var.
  • Une Data class ne peut pas être abstraite, ouverte à l'héritage, scellée ou interne (💡 mais des extensions sont possibles).

▶️ ce codeopen in new window illustrate les data class.

Programmation fonctionnelle

Concepts généraux

La programmation fonctionnelle s'articule autour de ces conceptsopen in new window : fonctions pures, récursivité, transparence référentielle, variables immuables, fonctions en tant que citoyens de première classe et fonctions d'ordre supérieur.

Expliquons brièvement ces concepts :

  • Les variables immuables signifient qu'on ne peut pas changer la valeur d'une variable ou ses propriétés une fois qu'elle a été créée. Si nous voulons le faire, nous devons créer une nouvelle instance avec la nouvelle valeur.
  • Les fonctions pures sont des fonctions qui n'ont pas d'effets secondaires et renverront donc toujours la même sortie étant donné la même entrée.
  • Les fonctions sont des citoyennes de première classe : elles peuvent être affectées à une variable ou utilisées dans des fonctions d'ordre supérieur (passées en tant qu'un argument de fonction ou retournées dans un fonction).
  • Transparence référentielleopen in new window : signifie qu'une expression peut être remplacée par son résultat sans modifier le comportement du programme.

💡 Les langages fonctionnels purs fournissent ces fonctionnalités de manière native et les appliquent (au moment de la construction).

Kotlin et programmation fonctionnelle

Kotlin n'est pas un langage fonctionnel pur mais il prend en charge certaines fonctionnalités. En effet, Kotlin ne sait pas dire si une fonction est pures ou non, mais il fournit des collections immuables via la bibliothèque kotlinx.collections.immutableopen in new window pour nous aider à manipuler des données immuables.

`listOf` génère des listes en lecture seule, mais qui sont mutables

Une liste en lecture seuleopen in new window ne peut pas ajouter ou supprimer des éléments, mais elle peut modifier les données sous-jacentes.

@Test
fun givenReadOnlyList_whenCastToMutableList_checkNewElementsAdded(){
    val list: List<String> = listOf("This", "Is", "Totally", "Immutable")
    (list as MutableList<String>)[2] = "Not"
    assertEquals(listOf("This", "Is", "Not", "Immutable"), list)
}

La librairie Arrow-ktopen in new window permet d'aller encore plus loin en développement fonctionnel.

Programmation déclarative

La programmation déclarative est un style célèbre dans la programmation fonctionnelle. Il consiste à écrire du code sous la forme d'un enchaînement d'appels de fonction dans ce style val result = f(x).g(y). .... Les fonctions d'ordre supérieur remplacent de nombreuses situations où nous utiliserions des boucles. Cela favorise le code lisible qui est facile à déboguer et à maintenir.

▶️ ce codeopen in new window montre comment manipuler une liste avec la programmation déclarative.

Kotlin and Java interoperability

  • Kotlin is designed with Java interoperability in mind.
  • Kotlin code may require some annotations to be called from Java.
  • It is possible to mix Java and Kotlin in the same project.
  • JetBrain's IntelliJ and Android Studio can convert to Kotlin when pasting java code.
  • Kotlin generates Java records by annotating a data class with @JvmRecord and targeting JVM 16, among other requirement listed hereopen in new window.
  • It is much more easier and natural to call Java from Kotlin.
    • For example: Java accessors are converted to Kotlin properties.

▶️ this codeopen in new window shows how to convert a Kotlin List to a Java ArrayList.

The official documentation provides exhaustive documentation on Kotlin and JVM integrationopen in new window

Exercices

Exercice 3

Voir l'exerciceopen in new window

Solution(s)

Solutionopen in new window

Exercice 4

Exerciceopen in new window

Solutions(s)

Solutionopen in new window

Exercice 5

Exerciceopen in new window

Solution(s)

Solutionopen in new window

Exercice 6

Exerciceopen in new window

solution(s)

Solutionopen in new window

Exercice 7

Exerciceopen in new window

Solution(s)

Solutionopen in new window

Plus d'exercices et de lecture