Pourquoi Kotlin? Huit fonctionnalités qui pourraient convaincre les développeurs Java de changer

Officiellement sorti en 2016, Kotlin a attiré beaucoup d'attention ces dernières années, d'autant plus que Google a annoncé son soutien à Kotlin comme alternative à Java sur les plateformes Android. Avec la décision récemment annoncée de faire de Kotlin la langue préférée pour Android, vous vous demandez peut-être s'il est temps de commencer à apprendre un nouveau langage de programmation. Si tel est le cas, cet article pourrait vous aider à décider.

Historique des versions de Kotlin

Kotlin a été annoncé en 2011, mais la première version stable, la version 1.0, n'est apparue qu'en 2016. Le langage est gratuit et open source, développé par JetBrains avec Andrey Breslav en tant que concepteur de langage principal. Kotlin 1.3.40 est sorti en juin 2019.

À propos de Kotlin

Kotlin est un langage de programmation moderne à typage statique qui présente à la fois des constructions de programmation orientées objet et fonctionnelles. Il cible plusieurs plates-formes, y compris la JVM, et est totalement interopérable avec Java. À bien des égards, Kotlin est ce à quoi Java pourrait ressembler s'il était conçu aujourd'hui. Dans cet article, je présente huit fonctionnalités de Kotlin que je pense que les développeurs Java seront ravis de découvrir.

  1. Syntaxe claire et compacte
  2. Système de type unique (presque)
  3. Sécurité nulle
  4. Fonctions et programmation fonctionnelle
  5. Classes de données
  6. Extensions
  7. Surcharge de l'opérateur
  8. Objets de niveau supérieur et modèle Singleton

Bonjour le monde! Kotlin contre Java

Le listing 1 montre le "Hello, world!" Obligatoire. fonction écrite en Kotlin.

Listing 1. "Bonjour tout le monde!" à Kotlin

 fun main() { println("Hello, world!") } 

Aussi simple soit-il, cet exemple révèle les principales différences par rapport à Java.

  1. mainest une fonction de premier niveau; autrement dit, les fonctions Kotlin n'ont pas besoin d'être imbriquées dans une classe.
  2. Il n'y a pas de public staticmodificateurs. Bien que Kotlin ait des modificateurs de visibilité, la valeur par défaut est publicet peut être omise. Kotlin ne prend pas non plus en charge le staticmodificateur, mais il n'est pas nécessaire dans ce cas car il mains'agit d'une fonction de niveau supérieur.
  3. Depuis Kotlin 1.3, le paramètre array-of-strings pour mainn'est pas obligatoire et peut être omis s'il n'est pas utilisé. Si nécessaire, il serait déclaré comme args : Array.
  4. Aucun type de retour n'est spécifié pour la fonction. Là où Java utilise void, Kotlin utilise Unit, et si le type de retour d'une fonction est Unit, il peut être omis.
  5. Il n'y a pas de point-virgule dans cette fonction. Dans Kotlin, les points-virgules sont facultatifs et les sauts de ligne sont donc significatifs.

C'est un aperçu, mais il y a beaucoup plus à apprendre sur la façon dont Kotlin diffère de Java et, dans de nombreux cas, l'améliore.

1. Syntaxe plus propre et plus compacte

Java est souvent critiqué pour être trop verbeux, mais une certaine verbosité peut être votre ami, surtout si cela rend le code source plus compréhensible. Le défi dans la conception du langage est de réduire la verbosité tout en conservant la clarté, et je pense que Kotlin va un long chemin pour relever ce défi.

Comme vous l'avez vu dans le listing 1, Kotlin ne nécessite pas de point-virgule et permet d'omettre le type de retour pour les Unitfonctions. Considérons quelques autres fonctionnalités qui aident à faire de Kotlin une alternative plus propre et plus compacte à Java.

Inférence de type

Dans Kotlin, vous pouvez déclarer une variable comme var x : Int = 5, ou vous pouvez utiliser la version plus courte mais tout aussi claire var x = 5. (Bien que Java prenne désormais en charge les vardéclarations, cette fonctionnalité n'est apparue qu'à partir de Java 10, longtemps après son apparition dans Kotlin.)

Kotlin a également des valdéclarations pour les variables en lecture seule, qui sont analogues aux variables Java qui ont été déclarées comme final, ce qui signifie que la variable ne peut pas être réaffectée. Le listing 2 en donne un exemple.

Listing 2. Variables en lecture seule dans Kotlin

 val x = 5 ... x = 6 // ERROR: WILL NOT COMPILE 

Propriétés versus champs

Là où Java a des champs, Kotlin a des propriétés. Les propriétés sont déclarées et accédées d'une manière similaire aux champs publics en Java, mais Kotlin fournit des implémentations par défaut des fonctions accesseur / mutateur pour les propriétés; autrement dit, Kotlin fournit des get()fonctions pour les valpropriétés et les deux get()et des set()fonctions pour les varpropriétés. Des versions personnalisées de get()et set()peuvent être mises en œuvre si nécessaire.

La plupart des propriétés de Kotlin auront des champs de sauvegarde, mais il est possible de définir une propriété calculée , qui est essentiellement une get()fonction sans champ de sauvegarde. Par exemple, une classe représentant une personne peut avoir une propriété pour dateOfBirthet une propriété calculée pour age.

Importations par défaut et importations explicites

Java importe implicitement les classes définies dans le package java.lang, mais toutes les autres classes doivent être importées explicitement. Par conséquent, de nombreux fichiers source Java commencent par importer des classes de collection à partir de java.util, des classes d' E / S à partir de java.io, etc. Par défaut, Kotlin implicitement les importations kotlin.*, ce qui est à peu près analogue à Java importation java.lang.*, mais aussi Kotlin les importations kotlin.io.*, kotlin.collections.*et les classes de plusieurs autres paquets. Pour cette raison, les fichiers source Kotlin nécessitent normalement moins d'importations explicites que les fichiers source Java, en particulier pour les classes qui utilisent des collections et / ou des E / S standard.

Aucun appel à «nouveau» pour les constructeurs

Dans Kotlin, le mot new- clé n'est pas nécessaire pour créer un nouvel objet. Pour appeler un constructeur, utilisez simplement le nom de la classe entre parenthèses. Le code Java

 Student s = new Student(...); // or var s = new Student(...); 

pourrait s'écrire comme suit en Kotlin:

 var s = Student(...) 

Modèles de chaîne

Les chaînes peuvent contenir des expressions de modèle , qui sont des expressions évaluées avec des résultats insérés dans la chaîne. Une expression de modèle commence par un signe dollar ($) et se compose d'un nom simple ou d'une expression arbitraire entre accolades. Les modèles de chaîne peuvent raccourcir les expressions de chaîne en réduisant le besoin de concaténation de chaînes explicite. À titre d'exemple, le code Java suivant

 println("Name: " + name + ", Department: " + dept); 

pourrait être remplacé par le code Kotlin plus court mais équivalent.

 println("Name: $name, Department: $dept") 

Prolonge et implémente

Les programmeurs Java savent qu'une classe peut avoir extendune autre classe et implementune ou plusieurs interfaces. Dans Kotlin, il n'y a pas de différence syntaxique entre ces deux concepts similaires; Kotlin utilise un deux-points pour les deux. Par exemple, le code Java

 public class Student extends Person implements Comparable 

serait écrit plus simplement en Kotlin comme suit:

 class Student : Person, Comparable 

Aucune exception vérifiée

Kotlin prend en charge les exceptions d'une manière similaire à Java avec une grande différence: Kotlin n'a pas d'exceptions vérifiées. Bien qu'elles aient été bien intentionnées, les exceptions vérifiées de Java ont été largement critiquées. Vous pouvez encore throwet catchexceptions, mais le compilateur Kotlin ne vous oblige pas à attraper l'un d'entre eux.

Destructuration

Pensez à la déstructuration comme un moyen simple de diviser un objet en ses parties constituantes. Une déclaration de déstructuration crée plusieurs variables à la fois. Le listing 3 ci-dessous fournit quelques exemples. Pour le premier exemple, supposons que la variable studentest une instance de classe Student, qui est définie dans le Listing 12 ci-dessous. Le deuxième exemple est tiré directement de la documentation Kotlin.

Listing 3. Destructuring examples

 val (_, lName, fName) = student // extract first and last name from student object // underscore means we don't need student.id for ((key, value) in map) { // do something with the key and the value } 

'if' statements and expressions

In Kotlin, if can be used for control flow as with Java, but it can also be used as an expression. Java's cryptic ternary operator (?:) is replaced by the clearer but somewhat longer if expression. For example, the Java code

 double max = x >= y ? x : y 

would be written in Kotlin as follows:

val max = if (x >= y) then x else y 

Kotlin is slightly more verbose than Java in this instance, but the syntax is arguably more readable.

'when' replaces 'switch'

My least favorite control structure in C-like languages is the switch statement. Kotlin replaces the switch statement with a when statement. Listing 4 is taken straight from the Kotlin documentation. Notice that break statements are not required, and you can easily include ranges of values.

Listing 4. A 'when' statement in Kotlin

 when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") } 

Try rewriting Listing 4 as a traditional C/Java switch statement, and you will get an idea of how much better off we are with Kotlin's when statement. Also, similar to if, when can be used as an expression. In that case, the value of the satisfied branch becomes the value of the overall expression.

Switch expressions in Java

Java 12 introduced switch expressions. Similar to Kotlin's when, Java's switch expressions do not require break statements, and they can be used as statements or expressions. See "Loop, switch, or take a break? Deciding and iterating with statements" for more about switch expressions in Java.

2. Single type system (almost)

Java has two separate type systems, primitive types and reference types (a.k.a., objects). There are many reasons why Java includes two separate type systems. Actually that's not true. As outlined in my article A case for keeping primitives in Java, there is really only one reason for primitive types--performance. Similar to Scala, Kotlin has only one type system, in that there is essentially no distinction between primitive types and reference types in Kotlin. Kotlin uses primitive types when possible but will use objects if necessary.

So why the caveat of "almost"? Because Kotlin also has specialized classes to represent arrays of primitive types without the autoboxing overhead: IntArray, DoubleArray, and so forth. On the JVM, DoubleArray is implemented as double[]. Does using DoubleArray really make a difference? Let's see.

Benchmark 1: Matrix multiplication

In making the case for Java primitives, I showed several benchmark results comparing Java primitives, Java wrapper classes, and similar code in other languages. One of the benchmarks was simple matrix multiplication. To compare Kotlin performance to Java, I created two matrix multiplication implementations for Kotlin, one using Array and one using Array . Listing 5 shows the Kotlin implementation using Array.

Listing 5. Matrix multiplication in Kotlin

 fun multiply(a : Array, b : Array) : Array { if (!checkArgs(a, b)) throw Exception("Matrices are not compatible for multiplication") val nRows = a.size val nCols = b[0].size val result = Array(nRows, {_ -> DoubleArray(nCols, {_ -> 0.0})}) for (rowNum in 0 until nRows) { for (colNum in 0 until nCols) { var sum = 0.0 for (i in 0 until a[0].size) sum += a[rowNum][i]*b[i][colNum] result[rowNum][colNum] = sum } } return result } 

Ensuite, j'ai comparé les performances des deux versions de Kotlin à celles de Java avec doubleet Java avec Double, en exécutant les quatre benchmarks sur mon ordinateur portable actuel. Puisqu'il y a une petite quantité de «bruit» dans l'exécution de chaque benchmark, j'ai exécuté toutes les versions trois fois et j'ai fait la moyenne des résultats, qui sont résumés dans le tableau 1.

Tableau 1. Performances d'exécution du benchmark de multiplication matricielle

Résultats chronométrés (en secondes)
Java

( double)

Java

( Double)

Kotlin

( DoubleArray)

Kotlin

( Array)

7h30 29,83 6,81 15,82

I was somewhat surprised by these results, and I draw two takeaways. First, Kotlin performance using DoubleArray is clearly superior to Kotlin performance using Array, which is clearly superior to that of Java using the wrapper class Double. And second, Kotlin performance using DoubleArray is comparable to--and in this example slightly better than--Java performance using the primitive type double.

Clearly Kotlin has done a great job of optimizing away the need for separate type systems--with the exception of the need to use classes like DoubleArray instead of Array.

Benchmark 2: SciMark 2.0

My article on primitives also included a second, more scientific benchmark known as SciMark 2.0, which is a Java benchmark for scientific and numerical computing available from the National Institute of Standards and Technology (NIST). The SciMark benchmark measures performance of several computational routines and reports a composite score in approximate Mflops (millions of floating point operations per second). Thus, larger numbers are better for this benchmark.

Avec l'aide d'IntelliJ IDEA, j'ai converti la version Java du benchmark SciMark en Kotlin. IntelliJ IDEA converti automatiquement double[]et int[]en Java vers DoubleArrayet IntArraydans Kotlin. J'ai ensuite comparé la version Java utilisant des primitives à la version Kotlin utilisant DoubleArrayet IntArray. Comme auparavant, j'ai exécuté les deux versions trois fois et j'ai fait la moyenne des résultats, qui sont résumés dans le tableau 2. Encore une fois, le tableau montre des résultats à peu près comparables.

Tableau 2. Performances d'exécution du benchmark SciMark

Performance (en Mflops)
Java Kotlin
1818,22 1815,78