Introduction à la langue Kotlin

1. Vue d’ensemble

Dans ce didacticiel, nous allons examiner Kotlin, un nouveau langage du monde JVM, et certaines de ses fonctionnalités de base, notamment les classes, l’héritage, les instructions conditionnelles et les constructions en boucle.

Nous examinerons ensuite certaines des principales caractéristiques qui font de Kotlin un langage attrayant, notamment la sécurité NULL, les classes de données, les fonctions d’extension et les modèles String .

2. Dépendances Maven

Pour utiliser Kotlin dans votre projet Maven, vous devez ajouter la bibliothèque standard Kotlin à votre pom.xml :

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>1.0.4</version>
</dependency>

Pour ajouter la prise en charge de JUnit à Kotlin, vous devez également inclure les dépendances suivantes:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-test-junit</artifactId>
    <version>1.0.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Enfin, vous devrez configurer les répertoires source et le plugin Kotlin pour pouvoir créer un build Maven:

<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    <plugins>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <groupId>org.jetbrains.kotlin</groupId>
            <version>1.0.4</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Vous pouvez trouver la dernière version de https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jetbrains.kotlin%22%20and%20a%3A%22kotlin-maven-plugin% 22[kotlin-maven-plugin]dans Maven Central.

3. Syntaxe de base

Examinons les éléments de base de la langue kotlin.

Il existe certaines similitudes avec Java (par exemple, la définition de packages s’applique de la même manière). Voyons les différences.

3.1. Définir les fonctions

Définissons une fonction ayant deux paramètres Int avec le type de retour Int :

fun sum(a: Int, b: Int): Int {
    return a + b
}

3.2. Définir des variables locales

Variable locale à affecter une seule fois (en lecture seule):

val a: Int = 1
val b = 1
val c: Int
c = 1

Notez que le type d’une variable b est déduit par un compilateur Kotlin. Nous pourrions aussi définir des variables mutables:

var x = 5
x += 1

4. Champs facultatifs

Kotlin a une syntaxe de base pour définir un champ pouvant être nullable (facultatif). Lorsque nous voulons déclarer que ce type de champ est nullable, nous devons utiliser le type suffixé d’un point d’interrogation:

val email: String?

Lorsque vous avez défini le champ nullable, il est parfaitement valide de lui affecter un null

val email: String? = null

Cela signifie que dans un champ de courrier électronique pourrait être un null. Si nous allons écrire:

val email: String = "value"

Ensuite, nous devons attribuer une valeur au champ email dans la même déclaration que nous déclarons email. Il ne peut pas avoir une valeur nulle. Nous reviendrons sur la sécurité à Kotlin dans une section ultérieure.

5. Des classes

Montrons comment créer une classe simple pour gérer une catégorie spécifique d’un produit. Notre classe ItemManager ci-dessous a un constructeur par défaut qui remplit deux champs - categoryId et dbConnection - et un champ facultatif email :

class ItemManager(val categoryId: String, val dbConnection: String) {
    var email = ""
   //...
}

Cette construction ItemManager (…​) crée un constructeur et deux champs dans notre classe: categoryId et dbConnection

Notez que notre constructeur utilise le mot-clé val pour ses arguments - cela signifie que les champs correspondants seront final et immuables.

Si nous avions utilisé le mot clé var (comme lors de la définition du champ email ), ces champs seraient mutables.

Créons une instance de ItemManager en utilisant le constructeur par défaut:

ItemManager("cat__id", "db://connection")

Nous pourrions construire ItemManager en utilisant des paramètres nommés. C’est très utile quand vous avez comme dans cet exemple une fonction qui prend deux paramètres du même type, par exemple. Chaîne, et vous ne voulez pas confondre un ordre d’eux. En utilisant des paramètres de dénomination, vous pouvez écrire explicitement le paramètre attribué. Dans la classe ItemManager , il y a deux champs, categoryId et dbConnection afin que les deux puissent être référencés à l’aide de paramètres nommés:

ItemManager(categoryId = "catId", dbConnection = "db://Connection")

C’est très utile lorsque nous devons passer plus d’arguments à une fonction.

Si vous avez besoin de constructeurs supplémentaires, définissez-les à l’aide du mot clé constructor . Définissons un autre constructeur qui définit également le champ email :

constructor(categoryId: String, dbConnection: String, email: String)
  : this(categoryId, dbConnection) {
    this.email = email
}

Notez que ce constructeur appelle le constructeur par défaut que nous avons défini ci-dessus avant de définir le champ email. Et comme nous avons déjà défini categoryId et dbConnection comme étant immuables à l’aide du mot clé val dans le constructeur par défaut, il n’est pas nécessaire de répéter le mot clé val dans le constructeur supplémentaire.

Maintenant, créons une instance en utilisant le constructeur supplémentaire:

ItemManager("cat__id", "db://connection", "[email protected]")

Si vous souhaitez définir une méthode d’instance sur ItemManager , utilisez le mot-clé fun :

fun isFromSpecificCategory(catId: String): Boolean {
    return categoryId == catId
}

6. Héritage

Par défaut, les classes de Kotlin sont fermées pour extension - l’équivalent d’une classe marquée final en Java.

Afin de spécifier qu’une classe est ouverte pour une extension, utilisez le mot clé open lors de la définition de la classe.

Définissons une classe Item ouverte à une extension:

open class Item(val id: String, val name: String = "unknown__name") {
    open fun getIdOfItem(): String {
        return id
    }
}

Notez que nous avons également noté la méthode getIdOfItem () ouverte. Cela permet de le remplacer.

Maintenant, étendons la classe Item et substituons la méthode getIdOfItem () :

class ItemWithCategory(id: String, name: String, val categoryId: String) : Item(id, name) {
    override fun getIdOfItem(): String {
        return id + name
    }
}

7. Expressions conditionnelles

En Kotlin, l’instruction conditionnelle if est l’équivalent d’une fonction qui renvoie une valeur. Regardons un exemple:

fun makeAnalyisOfCategory(catId: String): Unit {
    val result = if (catId == "100") "Yes" else "No"
    println(result)
}

Dans cet exemple, nous voyons que si catId est égal à «100», le bloc conditionnel renvoie «Oui», sinon il renvoie «Non» . La valeur renvoyée est affectée à result.

Vous pouvez créer un bloc if else normal:

val number = 2
if (number < 10) {
    println("number less that 10")
} else if (number > 10) {
    println("number is greater that 10")
}

Kotlin a également une commande très utile when qui agit comme une instruction switch avancée:

val name = "John"
when (name) {
    "John" -> println("Hi man")
    "Alice" -> println("Hi lady")
}

8. Collections

Il existe deux types de collections dans Kotlin: mutable et immuable.

Lorsque nous créons une collection immuable, cela signifie qu’il est en lecture seule:

val items = listOf(1, 2, 3, 4)

Il n’y a pas d’élément de fonction add sur cette liste.

Lorsque nous voulons créer une liste modifiable qui pourrait être modifiée, nous devons utiliser la méthode mutableListOf () :

val rwList = mutableListOf(1, 2, 3)
rwList.add(5)

Une liste mutable a la méthode add () afin que nous puissions y ajouter un élément.

Il existe également une méthode équivalente à d’autres types de collections:

mutableMapOf (), mapOf (), setOf (), mutableSetOf ()

9. Exceptions

Le mécanisme de gestion des exceptions est très similaire à celui de Java.

Toutes les classes d’exception se prolongent Throwable. L’exception doit avoir un message, un chemin de pile et une cause facultative. Chaque exception dans Kotlin est décochée , ce qui signifie que le compilateur ne nous oblige pas à les intercepter.

Pour lancer un objet exception, nous devons utiliser l’expression-throw:

throw Exception("msg")

Le traitement des exceptions se fait en utilisant try …​ catch block (finally optionnel)

try {

}
catch (e: SomeException) {

}
finally {

}

10. Lambdas

Dans Kotlin, nous pourrions définir des fonctions lambda et les transmettre comme arguments à d’autres fonctions.

Voyons comment définir un lambda simple:

val sumLambda = { a: Int, b: Int -> a + b }

Nous avons défini la fonction sumLambda qui prend deux arguments de type Int en tant qu’argument et renvoie Int.

Nous pourrions faire passer un lambda:

@Test
fun givenListOfNumber__whenDoingOperationsUsingLambda__shouldReturnProperResult() {
   //given
    val listOfNumbers = listOf(1, 2, 3)

   //when
    val sum = listOfNumbers.reduce { a, b -> a + b }

   //then
    assertEquals(6, sum)
}

11. Constructions en boucle

En Kotlin, la lecture en boucle des collections peut être réalisée à l’aide d’une construction standard for..in

val numbers = arrayOf("first", "second", "third", "fourth")
for (n in numbers) {
    println(n)
}

Si nous voulons itérer sur une plage d’entiers, nous pourrions utiliser une construction de plage:

for (i in 2..9 step 2) {
    println(i)
}

Notez que la plage dans l’exemple ci-dessus est inclusive des deux côtés. Le paramètre step est facultatif et revient à incrémenter le compteur deux fois à chaque itération. La sortie sera la suivante:

2
4
6
8

Nous pourrions utiliser une fonction rangeTo () définie sur la classe Int de la manière suivante:

1.rangeTo(10).map{ it **  2 }

Le résultat contiendra (notez que rangeTo () est également inclusif):

----[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]----

12. Sécurité nulle

Examinons l’une des principales caractéristiques de Kotlin - la sécurité nulle, intégrée au langage. Pour illustrer pourquoi cela est utile, nous allons créer un service simple qui renvoie un objet Item :

class ItemService {
    fun findItemNameForId(id: String): Item? {
        val itemId = UUID.randomUUID().toString()
        return Item(itemId, "name-$itemId");
    }
}

La chose importante à noter est le type retourné de cette méthode. C’est un objet suivi du point d’interrogation. C’est une construction du langage Kotlin, ce qui signifie que Item renvoyé par cette méthode pourrait être null.

Nous devons gérer ce cas au moment de la compilation, en décidant ce que nous voulons faire avec cet objet (il est plus ou moins équivalent à Java 8 Optional <T> type).

Si la signature de la méthode a un type sans point d’interrogation:

fun findItemNameForId(id: String): Item

alors le code appelant n’aura pas besoin de gérer un cas null car il est garanti par le compilateur et le langage Kotlin, cet objet renvoyé ne peut pas être null.

Sinon, si un objet nullable est passé à une méthode et que ce cas n’est pas géré, il ne sera pas compilé.

Écrivons un scénario de test pour la sécurité de type Kotlin:

val id = "item__id"
val itemService = ItemService()

val result = itemService.findItemNameForId(id)

assertNotNull(result?.let { it -> it.id })
assertNotNull(result!!.id)

Nous constatons ici qu’après l’exécution de la méthode findItemNameForId (), le type renvoyé est de Kotlin Nullable . Pour accéder à un champ de cet objet ( id ), nous devons gérer ce cas lors de la compilation. La méthode let () ne sera exécutée que si le résultat n’est pas nullable. Le champ Id est accessible à l’intérieur d’une fonction lambda car il est null safe.

Un autre moyen d’accéder à ce champ d’objet nullable consiste à utiliser l’opérateur Kotlin !! . Il est équivalent à:

if (result == null){
    throwNpe();
}
return result;

Kotlin vérifiera si cet objet est un null si tel est le cas, il lancera une NullPointerException, sinon il retournera un objet approprié.

La fonction throwNpe () est une fonction interne de Kotlin.

13. Classes de données

Une très belle construction de langage qui pourrait être trouvée dans Kotlin est la classe de données (elle est équivalente à la classe de cas du langage Scala). Le but de ces classes est de ne contenir que des données. Dans notre exemple, nous avions une classe Item qui ne contient que les données:

data class Item(val id: String, val name: String)

Le compilateur créera pour nous les méthodes hashCode () , equals () et toString () . Il est recommandé de rendre les classes de données immuables, en utilisant un mot clé val . Les classes de données peuvent avoir des valeurs de champ par défaut:

data class Item(val id: String, val name: String = "unknown__name")

Nous voyons que le champ name a une valeur par défaut “unknown name” .__

14. Fonctions d’extension

Supposons que nous ayons une classe faisant partie d’une bibliothèque tierce, mais que nous voulions l’étendre avec une méthode supplémentaire. Kotlin nous permet de le faire en utilisant des fonctions d’extension.

Prenons un exemple dans lequel nous avons une liste d’éléments et nous voulons en prendre un élément aléatoire. Nous voulons ajouter une nouvelle fonction random () à la classe tierce List .

Voici à quoi ça ressemble à Kotlin:

fun <T> List<T>.random(): T? {
    if (this.isEmpty()) return null
    return get(ThreadLocalRandom.current().nextInt(count()))
}

La chose la plus importante à noter ici est la signature de la méthode.

La méthode est préfixée du nom de la classe à laquelle nous ajoutons cette méthode supplémentaire.

A l’intérieur de la méthode d’extension, nous opérons sur une étendue de liste. Par conséquent, en utilisant this , vous avez accès aux méthodes d’instance de liste telles que isEmpty () ou __count (). est dans cette portée:

fun <T> getRandomElementOfList(list: List<T>): T? {
    return list.random()
}

Nous avons créé une méthode qui prend une liste, puis exécute la fonction d’extension personnalisée random () précédemment définie. Écrivons un scénario de test pour notre nouvelle fonction:

val elements = listOf("a", "b", "c")

val result = ListExtension().getRandomElementOfList(elements)

assertTrue(elements.contains(result))

La possibilité de définir des fonctions qui «étendent» les classes tierces est une fonctionnalité très puissante et peut rendre notre code plus concis et lisible.

15. Modèles de chaîne

Une très belle fonctionnalité du langage Kotlin est la possibilité d’utiliser des modèles pour __String s. C’est très utile car nous n’avons pas besoin de concaténer String __s manuellement:

val firstName = "Tom"
val secondName = "Mary"
val concatOfNames = "$firstName + $secondName"
val sum = "four: ${2 + 2}"

Nous pouvons également évaluer une expression dans le bloc $ \ {} :

val itemManager = ItemManager("cat__id", "db://connection")
val result = "function result: ${itemManager.isFromSpecificCategory("1")}"

16. Interopérabilité Kotlin/Java

L’interopérabilité entre Kotlin et Java est simple et transparente. Supposons que nous ayons une classe Java avec une méthode qui fonctionne sur String:

class StringUtils{
    public static String toUpperCase(String name) {
        return name.toUpperCase();
    }
}

Maintenant, nous voulons exécuter ce code depuis notre classe Kotlin. Nous avons seulement besoin d’importer cette classe et nous pourrions exécuter la méthode java à partir de Kotlin sans aucun problème:

val name = "tom"

val res = StringUtils.toUpperCase(name)

assertEquals(res, "TOM")

Comme nous le voyons, nous avons utilisé la méthode Java à partir du code Kotlin.

Appeler du code Kotlin depuis un Java est également très facile. Définissons la fonction simple de Kotlin:

class MathematicsOperations {
    fun addTwoNumbers(a: Int, b: Int): Int {
        return a + b
    }
}

L’exécution de addTwoNumbers () à partir de code Java est très simple:

int res = new MathematicsOperations().addTwoNumbers(2, 4);

assertEquals(6, res);

Nous voyons que l’appel au code Kotlin était transparent pour nous.

Lorsque nous définissons une méthode en Java dont le type de retour est un void , la valeur renvoyée dans Kotlin sera du type Unit .

Il existe des identifiants spéciaux en langage Java ( is , object , in , ..) qui, lorsqu’ils sont utilisés dans le code Kotlin, doivent être protégés. Par exemple, nous pourrions définir une méthode qui porte le nom object () mais nous devons nous rappeler d’échapper à ce nom car il s’agit d’un identifiant spécial java:

fun `object`(): String {
    return "this is object"
}

Ensuite, nous pourrions exécuter cette méthode:

`object`()

17. Conclusion

Cet article est une introduction à la langue Kotlin et à ses principales caractéristiques. Il commence par introduire des concepts simples comme les boucles, les instructions conditionnelles et la définition de classes. Puis montre quelques fonctionnalités plus avancées telles que les fonctions d’extension et la sécurité NULL.

Vous trouverez la mise en œuvre de tous ces exemples et extraits de code à l’adresse le projet GitHub - c’est un projet Maven, il devrait donc être facile de importer et exécuter tel quel.