Tutoriel Kotlin : vos premiers pas avec ce nouveau langage de programmation
Disponible depuis 2016 seulement (avec la version 1.0), mais déjà très apprécié, Kotlin est une alternative à Java. Ce langage de programmation orienté objet a été développé par JetBrains, une entreprise tchèque de développement de logiciels. Il séduit de nombreux développeurs par son caractère léger, mais aussi par la faible survenance d’erreurs d’exécution, en particulier les erreurs NullPointerExceptions très redoutées. Kotlin est tout particulièrement apprécié pour le développement d’applications Android, mais rencontre également un immense succès comme base de départ pour des applications JavaScript.
Kotlin s’inspire fortement de Java : ces deux langages de programmation ne sont certes pas compatibles, mais Kotlin peut être transformé en bytecode lisible par une machine virtuelle Java (JVM).
Introduction à Kotlin avec des exemples
Pour démarrer avec Kotlin, vous pouvez télécharger ce compilateur sur le site Internet officiel. L’installation est facilitée en cas d’utilisation d’un environnement de développement (IDE) : IntelliJ IDEA (également développé par JetBrains), Eclipse (avec le plugin correspondant), NetBeans et Android Studio peuvent par exemple supporter Kotlin.
Les fabricants de Kotlin mettent à disposition une sandbox en ligne, dans laquelle vous pourrez tester tous les exemples.
Packages
Au début d’un projet, vous importez les packages dont vous avez besoin pour le réaliser. Vous définissez également le package sur lequel vous travaillez actuellement. Les packages contiennent des classes et des fonctions.
package test.bar
import foo.bar
import footoo.bar as footoo
Afin d’éviter tout problème de noms identiques, Kotlin vous permet de renommer les packages avec as. L’attribution d’un alias aux packages n’impose pas de suivre la structure du répertoire dans lequel ils se trouvent. Il est toutefois recommandé de procéder de cette façon pour permettre une vue d’ensemble.
Kotlin charge automatiquement les principaux packages dans chaque projet.
Contrairement à Java, Kotlin vous permet également d’importer des fonctions individuelles depuis d’autres packages. Pour ce faire, on indique le chemin correct :
import foo.bar.myFunction
La fonction peut ensuite être utilisée tout à fait normalement.
Dans Kotlin, il n’est pas obligatoire de terminer les lignes de code par un marqueur, par exemple un point-virgule.
Variables
Kotlin connaît deux types de variables : les variables immuables, qui sont en lecture seule, sont introduites par val. Les autres variables, dont la valeur est modifiable au fil du programme, sont introduites par var.
val name = "Clara Oswald"
var age = 22
Contrairement au nom, qui est fixe, l’âge peut être adapté, par exemple dans une fonction.
Dans cet exemple, Kotlin a déterminé seul le type de valeur des variables. Il est également possible d’indiquer individuellement ces types de base.
Types de base (basic types)
Kotlin travaille avec certains types de variables et de classes. Chaque type est un objet, ce qui distingue quelque peu Kotlin de Java. Alors que les langages de programmation plus anciens doivent tout d’abord placer les types primitifs dans un wrapper pour qu’ils se comportent comme des objets, avec Kotlin, c’est inutile puisque tous les types sont d’ores et déjà des objets.
Nombres (numbers)
Dans Kotlin, vous pouvez utiliser des nombres sans aucune balise : le compilateur comprend que ce sont des valeurs numériques. Les virgules sont réalisées à l’aide de points. Il est également possible d’utiliser des nombres hexadécimaux. Afin de permettre une meilleure lisibilité, les séparateurs de milliers sont représentés à l’aide de tirets. Kotlin connaît différents types de nombres pouvant tous avoir une taille maximale différente :
- Long : 64 bits
- Int : 32 bits
- Short : 16 bits
- Byte : 8 bits
- Double : 64 bits
- Float : 32 bits
Pour Double et Float, il s’agit de nombres flottants qui ne se comportent pas comme les nombres fixes dans des calculs complexes. Comme pour tous les types, vous pouvez indiquer des nombres de façon concrète dans votre code.
val myNumber: Long = 40
Il est possible de convertir un nombre d’un type en nombre d’un autre type.
val myInt = 600
val myLong= myInt.toLong()
La commande toLong convertit la valeur « Int » en valeur « Long ». La commande fonctionne de façon analogue pour les autres types de nombres.
String
Un string est un ensemble de mots ou des phrases complètes, autrement dit, une chaîne de caractères. Pour utiliser un string dans Kotlin, placez le texte entre des guillemets doubles. Si vous souhaitez intégrer plusieurs lignes de texte, il est nécessaire d’ajouter trois guillemets doubles au début et à la fin (raw string).
val myString = "Ce string comporte une seule ligne."
val myLongString = """Ce string s'étend
sur plusieurs lignes."""
Comme dans de nombreux langages de programmation, Kotlin permet l’utilisation de caractères d’échappement : une barre oblique inversée permet de désigner un caractère ne faisant pas partie du string et devant être traité comme un caractère de contrôle. À l’inverse, une barre oblique inversée permet également d’insérer dans le string des caractères ayant normalement une autre signification dans Kotlin. Les caractères d’échappement suivants sont possibles :
- \t : tabulation
- \b : retour arrière
- \n : nouvelle ligne
- \r : retour chariot
- \' : guillemets simples
- \" : guillemets doubles
- \\ : barre oblique inversée
- \$ : symbole dollar
Dans les strings, le symbole dollar sert à indiquer une balise. Il est possible de la définir comme variable lors d’une étape préalable. La balise est alors remplacée par une véritable valeur dans l’édition.
val author = "Sandra"
val myString = "Ce texte a été écrit par $author"
Caractères (characters)
Pour certains caractères, Kotlin met également à disposition le type de données spécifique Character. Plutôt que de l’indiquer entre des guillemets doubles, on utilise des guillemets simples.
var model = 'A'
Boolean
Le type de base Boolean traduit une valeur de vérité qui peut être définie sur vrai (true) ou faux (false).
Arrays Kotlin
Dans Kotlin, un array est une collection de données. Vous pouvez construire un array avec arrayOf() ou Array(). La première de ces fonctions est simple :
val myArray1 = arrayOf(0, 1, 2, 3, 4, 5)
On génère ainsi un array avec des chiffres de 1 à 5. Ces collections peuvent toutefois abriter d’autres types, comme des strings et des booleans, voire un mélange des deux. Si on souhaite limiter l’array à un type, il suffit de l’indiquer dans la fonction.
val myArray2 = arrayOf<int>(10, 20, 30)</int>
val myArray3 = booleanArrayOf(true, true, false)
Le constructor Kotlin Array() est plus complexe : vous devez également indiquer la longueur et une fonction lambda.
val myArray4 = Array(6, { i -> i })
Le constructor Kotlin génère un array avec six chiffres en commençant par zéro : 0, 1, 2, 3, 4, 5.
Vous en apprendrez davantage sur les constructors et les fonctions lambda dans la suite de l’article.
Chaque entrée d’un array Kotlin est indexée et peut être appelée à l’aide de cet index. On utilise ici des crochets dans lesquels l’emplacement comportant l’entrée dans la liste est indiqué.
fun main() {
val myArray5 = arrayOf("Jan", "Maria", "Samuel")
println(myArray5[2])
}
La fonction indiquera dans ce cas « Samuel » puisque l’énumération commence par 0.
Opérateurs
Comme dans de nombreux autres langages de programmation, Kotlin utilise différents opérateurs pouvant être intégrés au code source. On trouve notamment les opérateurs arithmétiques (+, -, *, /, %), les opérateurs de comparaison (<, >, <=, >=, ==, !=) et les opérateurs logiques (&&, ||, !). Les mots-clés sont étroitement liés aux opérateurs. Il s’agit de termes ayant une signification fixe dans Kotlin et ne pouvant pas être réinterprétés.
Une liste exhaustive de tous les mots-clés et opérateurs est disponible dans la documentation officielle de Kotlin.
Intervalles (ranges)
Dans Kotlin, un Range est un type qui va d’un point à un autre. Pour générer un intervalle de ce type, on utilise l’opérateur .. ou les fonctions rangeTo() ou downTo(). La variante avec deux points permet un incrément. Les deux fonctions vous permettent en revanche de définir un sens.
val range1 = 1..5
val range2 = 1.rangeTo(5)
val range3 = 5.downTo(1)
Dans ces deux variantes simples, vous générez un intervalle avec un incrément de un. Pour modifier l’incrément, ajoutez la fonction step.
val range4 = 0..10 step(2)
Pour traiter des données individuelles de l’intervalle, utilisez l’opérateur in. Vous pouvez par exemple générer une requête ou une boucle. Afin de vérifier si une valeur ne fait pas partie de l’intervalle, on utilise l’opérateur !in.
val range5 = 0..10
fun main() {
for (n in range5) {
println(n)
}
if (7 in range5) {
println("yes")
}
if (12 !in range5) {
println("no")
}
}
Vous en apprendrez davantage sur les fonctions, les boucles et les requêtes dans la suite de l’article.
Fonctions (functions) Kotlin
Les fonctions Kotlin sont toujours créées avec la commande fun. Vous définissez ensuite le nom de la fonction, ses arguments et enfin, son effet.
fun div(a: Int, b: Int): Int {
return a/b
}
fun main() {
println(div(100, 2))
}
Nous définissons dans un premier temps la fonction div (pour division) avec deux paramètres Int a et b. Cette fonction doit également nous donner le résultat de la division de a par b sous la forme d’une variable Int. Finalement, dans la fonction main, nous appelons la fonction définie précédemment, lui donnons des valeurs concrètes et affichons le résultat à l’aide de println (print line) dans la console. Kotlin exécute automatiquement le contenu de main(). Cette fonction constitue le point de départ d’un programme Kotlin.
Kotlin n’accepte aucune commande en dehors des fonctions. Seules les déclarations y sont autorisées.
Dans Kotlin, les fonctions comportant une seule ligne de code sont facilement représentées. Plutôt que d’ouvrir une accolade, d’écrire une nouvelle ligne intercalée et de refermer l’accolade, on utilise le symbole égal. Il est ainsi possible de se passer de la commande return.
fun div(a: Int, b: Int): Int = a/b
fun main() = println(div(100, 2))
Afin d’éviter une erreur due à des paramètres erronés, vous pouvez indiquer des valeurs standard lors de la définition de la fonction. Si les paramètres sont laissés libres lors de l’appel de la fonction, les valeurs par défaut seront utilisées.
fun div(a: Int = 10, b: Int = 5): Int = a/b
fun main() = println(div())
Lambdas
Une fonction lambda (ou fonction anonyme) est une fonction qui n’appartient ni à une classe ni à un objet. Les fonctions lambda sont placées directement dans d’autres fonctions ou variables. Elles sont appelées sans utiliser le mot-clé fun. En principe, les fonctions lambda s’utilisent comme les variables de type val et sont également générées de cette façon.
fun main() {
val myMessage = { println("Coucou tout le monde !") }
myMessage()
}
Dans Kotlin, les expressions lambda doivent toujours être placées dans des accolades et peuvent également traiter des arguments de fonction. Ces derniers sont identifiés par une flèche qui sépare les paramètres du noyau de l’expression.
fun main() {
val div = {a: Int, b: Int -> a/b}
println(div(6,2))
}
Classes
À l’instar de Java, les classes de Kotlin sont des collections de données et de fonctions. Pour définir une classe, il suffit d’utiliser le mot-clé class et de compléter les informations de la nouvelle classe.
class Tardis {
var year: Int
var place: String
constructor(year: Int, place: String) {
this.year = year
this.place = place
}
}
Dans Kotlin, le constructor est une fonction utilisée pour créer des objets. Pour y parvenir, le langage de programmation utilise des primary et des secondary constructors (constructeurs primaires et secondaires). Les primary constructors sont une syntaxe abrégée pratique alors que les secondary constructors sont une forme de syntaxe similaire à celle de nombreux autres langages orientés objet dont fait notamment partie Java. Cette seconde variante est celle présentée dans l’exemple ci-dessus.
Il est toutefois possible de faire l’impasse sur le secondary constructor et d’utiliser plutôt un primary constructor. Écrivez ce constructeur directement dans la ligne d’en-tête de la classe et indiquez également les paramètres de la classe. Le nombre de lignes de code s’en trouve nettement réduit.
class Tardis constructor(var year: Int, var place: String)
Si vous ne souhaitez pas apporter d’informations complémentaires sur la visibilité de la classe (public, private, protected), vous pouvez tout à fait vous passer entièrement du mot-clé.
class Tardis (var year: Int, var place: String)
Ces trois exemples de code génèrent le même résultat.
Vous pouvez à présent utiliser cette classe dans le reste de votre code source et le compléter avec des valeurs concrètes.
val tardis1 = Tardis(2133, "Dunlop Station")
val tardis2 = Tardis(1885, "Northhampton")
Comme dans la plupart des langages orientés objet, vous pouvez également accéder directement aux propriétés et aux méthodes d’un objet en plaçant un point et le nom de la propriété ou de la méthode derrière l’objet.
class Tardis (var year: Int, var place: String)
val tardis1 = Tardis(2133, "Dunlop Station")
val tardis2 = Tardis(1885, "Northhampton")
fun main() {
println(tardis1.year)
}
La data class constitue une particularité de Kotlin. Ce type de classe est conçu pour enregistrer uniquement des données. En principe, il suffit pour cela d’une ligne de code.
data class User (var username: String, var name: String, var age: Int)
Cette classe peut être utilisée directement.
data class User (var username: String, var name: String, var age: Int)
fun main() {
val user1 = User ("River Song", "Melody Pond", 200)
println("Username: " + user1.username)
println("Name: " + user1.name)
println("Age: " + user1.age)
}
Objets (objects)
Dans Kotlin, les objets sont des instances qui ne peuvent être définies qu’une seule fois de cette façon (singleton). Ces objets contiennent généralement des variables et des fonctions. Ils sont en principe créés de façon similaire aux classes, c’est-à-dire avec une seule ligne de code. À sa création, l’objet est vide. Son contenu est indiqué dans le corps de l’objet.
object myObject {
fun sum(a: Int, b: Int): Int {
return a+b
}
}
Boucles (loops)
Dans Kotlin, trois types de boucles sont disponibles : while, do..while et if. Elles se comportent comme leurs équivalents dans les autres langages de programmation. Une boucle while se poursuit jusqu’à ce qu’une condition déterminée survienne.
fun main() {
var n = 1
while (n <= 10) {
println(n++)
}
}
La boucle do..while se comporte de façon similaire à la boucle while avec pour différence que le contenu de la boucle est parcouru au minimum une fois puisque la vérification a lieu uniquement à la fin.
fun main() {
var n = 1
do {
n++
}
while (n < 1)
println(n)
}
La boucle for se poursuit tant qu’une condition reste valable.
val myRange = 0..10
fun main() {
for (n in myRange) {
print("$n ")
}
}
Conditions
Kotlin offre trois possibilités pour appliquer certaines instructions ou ramifications : if, if..else et when. La requête if permet à l’ordinateur de réaliser une tâche lorsque la condition est satisfaite.
val whoCompanion = arrayOf("Bill Potts", "Clara Oswald", "Amy Pond", "Martha Jones", "Donna Noble", "Rose Tyler")
fun main() {
if ("Rose Tyler" in whoCompanion) {
print("yes")
}
}
else permet d’ajouter une action qui sera exécutée lorsque la condition n’est pas satisfaite.
val whoCompanions9 = arrayOf("Rose Tyler")
val whoCompanions10 = arrayOf("Martha Jones", "Donna Noble", "Rose Tyler")
val whoCompanions11 = arrayOf("Clara Oswald", "Amy Pond")
val whoCompanions12 = arrayOf("Bill Potts", "Clara Oswald")
fun main() {
var whoCompanion = "Clara Oswald"
if (whoCompanion in whoCompanions9) {
print("yes")
}
else {
print("no")
}
}
Finalement, l’expression when est une spécificité de Kotlin : des actions différentes sont réalisées en fonction des différents états. L’effet de l’expression when est ainsi similaire à ce celui de l’expression switch dans d’autres langages de programmation, mais il permet davantage de précision.
Placez les différents cas de vérification dans le corps de when. Ils sont toujours associés à une variable définie.
var age = 17
fun main() {
when {
age > 18 -> println("Tu es trop vieux !")
age == 18 -> println("Déjà adulte !")
age == 17 -> println("Bienvenue !")
age <= 16 -> println("Tu es trop jeune !")
}
}
L’argument peut toutefois être transmis directement à when et ne doit pas être répété à chaque fois dans le corps. Par ailleurs, une condition unique peut déclencher plusieurs actions. Pour cela, créez un nouveau corps avec des accolades. L’expression else vous aidera à exclure les cas imprévus.
fun multi(a: Int, b: Int, c: Int): Int {
return a*b*c
}
fun main() {
val d = "yes"
when (d) {
"no" -> println("Aucun calcul")
"yes" -> {
println("Démarrer le calcul")
println(multi(5, 2, 100))
println("Calcul terminé")
}
else -> println("Saisie erronée")
}
}
Nullability
L’erreur NullPointerException est une source de problèmes importante dans le cas d’une programmation avec Java. Cette erreur survient lorsqu’on fait référence à un objet dont la valeur est null. Kotlin contourne ce problème en refusant d’emblée que les variables prennent la valeur null. Si le cas se présentait, le message « Null can not be a value of a non-null type String » ou un autre message correspondant apparaîtrait avant la compilation.
Il existe toutefois des situations où l’on souhaite utiliser la valeur null à dessein. Pour y parvenir, Kotlin utilise l’opérateur safe call : ?.
fun main() {
var password: String? = null
print(password)
}
En indiquant ce symbole, vous précisez explicitement à Kotlin que la valeur null est acceptable. Le programme indiquera alors null. Toutefois, si vous souhaitez traiter une propriété spécifique de cette variable, vous devrez de nouveau utiliser l’opérateur safe call.
fun main() {
var password: String? = null
print(password?.length)
}
Ce code produira à nouveau la valeur null, mais aucune erreur et le programme fonctionnera. Il est toutefois plus élégant d’indiquer une valeur alternative. Pour cela, utilisez l’opérateur Elvis ?: ainsi nommé en raison de la banane que décrit le smiley formé par cette séquence de caractères.
fun main() {
val firstName = null
val lastName = "Pond"
val name: String = firstName?: "Prénom manquant" + " " + lastName?: "Nom manquant"
print(name)
}
Dans cet exemple, des informations seront affichées lorsqu’une variable aura la valeur null.