Nous vous expliquons ici les modifications apportées aux bibliothèques standard et de test dans la version 1.5.0-RC. Il s'agit notamment de :
- types entiers non signés stables ;
- extensions pour java.nio.file.Path ;
- améliorations de l'API String et Char ;
- changements pour l'API Duration ;
- nouvelles opérations mathématiques pour l'arithmétique modulaire ;
- nouvelles fonctions de collecte ;
- changements pour la bibliothèque de test.
Types entiers non signés stables
La bibliothèque standard inclut l'API d'entiers non signés qui est utile pour traiter les opérations d'entiers non négatifs. Elle inclut :
- les types de nombres non signés : UInt, ULong, UByte, UShort et fonctions associées, telles que les conversions ;
- les types d'agrégations : tableaux, plages et progressions d'entiers non signés, à savoir UIntArray, UIntRange et conteneurs similaires pour d'autres types.
Les types entiers non signés sont disponibles en version bêta depuis Kotlin 1.3. JetBrains classe maintenant les types entiers et les opérations non signés comme stables, les rendant disponibles par défaut et fiables à utiliser dans des projets réels. Les nouvelles API stables sont :
- les types d'entiers non signés ;
- les plages et progressions de types entiers non signés ;
- les fonctions qui utilisent des types entiers non signés.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | val zero = 0U // Define unsigned numbers with literal suffixes val ten = 10.toUInt() // or by converting non-negative signed numbers //val minusOne: UInt = -1U // Error: unary minus is not defined val range: UIntRange = zero..ten // Separate types for ranges and progressions for (i in range) print(i) println() println("UInt covers the range from ${UInt.MIN_VALUE} to ${UInt.MAX_VALUE}") // UInt covers the range from 0 to 4294967295 |
Les tableaux d'entiers non signés restent en version bêta. Si vous souhaitez les utiliser dans votre code, vous pouvez les activer avec l'annotation @ExperimentalUnsignedTypes.
Extensions pour l'API java.nio.file.Path
Kotlin fournit maintenant un moyen d'utiliser les entrées/sorties Java modernes non bloquantes dans un style Kotlin prêt à l'emploi via les fonctions d'extension pour java.nio.file.Path.
Voici un petit exemple :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import kotlin.io.path.* import java.nio.file.Path fun main() { // construct path with the div (/) operator val baseDir = Path("/base") val subDir = baseDir / "subdirectory" // list files in a directory val kotlinFiles = Path("/home/user").listDirectoryEntries("*.kt") // count lines in all kotlin files val totalLines = kotlinFiles.sumOf { file -> file.useLines { lines -> lines.count() } } } |
Ces extensions ont été introduites en tant que fonctionnalités expérimentales dans Kotlin 1.4.20, et sont maintenant disponibles par défaut. Vous pouvez consulter le package kotlin.io.path pour la liste des fonctions que vous pouvez utiliser. Les extensions existantes pour File API restent disponibles, vous êtes donc libre de choisir l'API que vous préférez.
API indépendante des paramètres régionaux pour les majuscules et les minuscules
Beaucoup d'entre vous connaissent les fonctions stdlib pour changer la casse des chaînes et des caractères : toUpperCase(), toLowerCase(), toTitleCase(). Ils fonctionnent généralement bien, mais ils peuvent être des casse-têtes lorsqu'il s'agit de gérer différents paramètres régionaux de plateforme - ils sont tous sensibles aux paramètres régionaux, ce qui signifie que leur résultat peut différer selon les paramètres régionaux. Par exemple, que renvoie "Kotlin".toUpperCase() ? Vous direz évidemment « KOTLIN ». Mais dans la langue turque, la capitale i est İ, donc le résultat est différent : KOTLİN.
Il existe maintenant une nouvelle API indépendante des paramètres régionaux pour changer la casse des chaînes et des caractères : les extensions uppercase(), lowercase(), titlecase() et leurs équivalents *Char(). Vous les avez déjà peut-être essayés en préversion dans la version 1.4.30.
Les nouvelles fonctions se comportement de la même manière quels que soient les paramètres régionaux de la plateforme. Appelez simplement ces fonctions et laissez la stdlib s'occuper du reste.
Code : | Sélectionner tout |
1 2 3 4 5 6 | // replace the old API println("Kotlin".toUpperCase()) // KOTLIN or KOTLİN or?.. // with the new API println("Kotlin".uppercase()) // Always KOTLIN |
Sur la JVM, vous pouvez effectuer un changement de casse sensible aux paramètres régionaux en appelant les nouvelles fonctions avec les paramètres régionaux actuels comme argument :
Code : | Sélectionner tout |
1 2 | "Kotlin".uppercase(Locale.getDefault()) // Locale-sensitive uppercasing |
Les nouvelles fonctions remplaceront complètement les anciennes, que JetBrains abandonne maintenant.
Effacer les conversions de caractères en code et de caractères en chiffres
L'opération pour obtenir un code UTF-16 d'un caractère - la fonction toInt() - piégeait fréquemment les développeurs, car elle est similaire à String.toInt() sur des chaînes à un chiffre qui produisent un Int présenté par ce chiffre.
Code : | Sélectionner tout |
1 2 3 | "4".toInt() // returns 4 '4'.toInt() // returns 52 |
De plus, il n'y avait aucune fonction commune qui renverrait la valeur numérique 4 pour le caractère « 4 ».
Pour résoudre ces problèmes, il existe désormais un ensemble de nouvelles fonctions de conversion entre les caractères et leurs codes entiers et valeurs numériques :
- Char(code) et Char.code font la conversion entre un caractère et son code ;
- Char.digitToInt(radix: Int) et sa version *OrNull créent un entier à partir d'un chiffre dans la base spécifiée ;
- Int.digitToChar(radix: Int) crée un caractère à partir d'un chiffre qui représente un entier dans la base spécifiée.
Ces fonctions ont des noms clairs et rendent le code plus lisible :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | val capsK = Char(75) // ‘K’ val one = '1'.digitToInt(10) // 1 val digitC = 12.digitToChar(16) // hexadecimal digit ‘C’ println("${capsK}otlin ${one}.5.0-R${digitC}") // “Kotlin 1.5.0-RC” println(capsK.code) // 75 |
Les nouvelles fonctions sont disponibles depuis Kotlin 1.4.30 en mode préversion et sont désormais stables. Les anciennes fonctions de conversion de caractère en nombre (Char.toInt() et fonctions similaires pour d'autres types numériques) et de conversion de nombre en caractère (Long.toChar() et fonctions similaires à l'exception de Int.toChar()) sont désormais obsolètes.
Fonctions Char multiplateformes
JetBrains continue d'étendre la partie multiplateforme de la bibliothèque standard pour fournir toutes ses fonctionnalités au code commun de projet multiplateforme. L'entreprise a maintenant rendu un certain nombre de fonctions Char disponibles sur toutes les plateformes et dans un code commun. Ces fonctions sont :
- Char.isDigit(), Char.isLetter(), Char.isLetterOrDigit() qui vérifient si un caractère est une lettre ou un chiffre ;
- Char.isLowerCase(), Char.isUpperCase(), Char.isTitleCase() qui vérifient la casse d'un caractère ;
- Char.isDefined() qui vérifie si un caractère a une catégorie générale Unicode autre que Cn (non défini) ;
- Char.isISOControl() qui vérifie si un char est un caractère de contrôle ISO, qui a un code dans les plages \u0000..\u001F ou \u007F..\u009F.
La propriété Char.category et sa classe d'énumération de type de retour CharCategory, qui indique la catégorie générale d'un caractère selon Unicode, sont désormais disponibles dans les projets multiplateformes.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | val array = "Kotlin 1.5.0-RC".toCharArray() val (letterOrDigit, punctuation) = array.partition { it.isLetterOrDigit() } val (upperCase, notUpperCase ) = array.partition { it.isUpperCase() } println("$letterOrDigit, $punctuation") // [K, o, t, l, i, n, 1, 5, 0, R, C], [ , ., ., -] println("$upperCase, $notUpperCase") // [K, R, C], [o, t, l, i, n, , 1, ., 5, ., 0, -] if (array[0].isDefined()) println(array[0].category) |
Versions strictes de String?.toBoolean()
La fonction String?.toBoolean() de Kotlin est largement utilisée pour créer des valeurs booléennes à partir de chaînes. Elle fonctionne de manière assez simple : elle est vraie sur une chaîne "true" quelle que soit sa casse et false sur toutes les autres chaînes, y compris null.
Bien que ce comportement semble naturel, il peut masquer des situations potentiellement erronées. Quoi que vous convertissiez avec cette fonction, vous obtenez un booléen même si la chaîne a une valeur inattendue. De nouvelles versions strictes sensibles à la casse de String?.toBoolean() sont là pour éviter de telles erreurs :
- String.toBooleanStrict() lève une exception pour toutes les entrées à l'exception des littéraux « true » et « false » ;
- String.toBooleanStrictOrNull() renvoie null pour toutes les entrées à l'exception des littéraux « true » et « false ».
Code : | Sélectionner tout |
1 2 3 4 5 | println("true".toBooleanStrict()) // True // println("1".toBooleanStrict()) // Exception println("1".toBooleanStrictOrNull()) // null println("True".toBooleanStrictOrNull()) // null: the function is case-sensitive |
Modifications de l'API de durée
L'API expérimentale de mesure de la durée et du temps est disponible dans stdlib depuis la version 1.3.50. L'une des classes clés de cette API est Duration. Elle représente le laps de temps entre deux instants de temps. Dans la version 1.5.0, Duration reçoit des changements significatifs à la fois dans l'API et la représentation interne.
Duration utilise désormais une valeur Long pour la représentation interne au lieu de Double. La plage de valeurs Long permet de représenter plus de cent ans avec une précision nanoseconde ou cent millions d'années avec une précision milliseconde. Cependant, les durées inférieures à la nanoseconde précédemment prises en charge ne sont plus disponibles.
JetBrains introduit également de nouvelles propriétés pour récupérer une durée en tant que valeur Long. Elles sont disponibles pour différentes unités de temps : Duration.inWholeMinutes, Duration.inWholeSeconds et autres. Ces fonctions viennent remplacer les propriétés basées sur Double telles que Duration.inMinutes.
Un autre changement est un ensemble de nouvelles fonctions permettant de créer des instances de durée à partir de valeurs entières. Elles sont définies directement dans le type Duration et remplacent les anciennes propriétés d'extension des types numériques tels que Int.seconds.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | import kotlin.time.ExperimentalTime import kotlin.time.Duration @ExperimentalTime fun main() { val duration = Duration.milliseconds(120000) println("There are ${duration.inWholeSeconds} seconds in ${duration.inWholeMinutes} minutes") } |
Compte tenu de ces changements majeurs, toute l'API de mesure de durée et de temps reste expérimentale dans la version 1.5.0 et doit donc être activée avec l'annotation @ExperimentalTime.
Opérations mathématiques : division entière "floor" et opérateur mod
Dans Kotlin, l'opérateur de division (/) sur les entiers représente la division tronquée, qui supprime la partie fractionnaire du résultat. En arithmétique modulaire, il existe également une alternative - division "floor" qui arrondit le résultat vers la valeur entière inférieure, ce qui produit un résultat différent sur les nombres négatifs.
Auparavant, la division floor nécessitait une fonction personnalisée telle que :
Code : | Sélectionner tout |
1 2 3 4 5 6 | fun floorDivision(i: Int, j: Int): Int { var result = i / j if (i != 0 && result <= 0) result-- return result } |
Dans Kotlin 1.5.0-RC, JetBrains introduit la fonction floorDiv() qui effectue une division floor sur les entiers.
Code : | Sélectionner tout |
1 2 3 | println("Truncated division -5/3: ${-5 / 3}") println("Floored division -5/3: ${(-5).floorDiv(3)}") |
Dans la version 1.5.0 de Kotlin, JetBrains introduit la nouvelle fonction mod(). Elle fonctionne maintenant exactement comme son nom l'indique - elle renvoie le modulo qui est le reste de la division floor.
Cette fonction diffère de rem() (ou opérateur %) de Kotlin. Le modulo est la différence entre a et a.floorDiv(b) * b. Le modulo non nul a toujours le même signe que b tandis que a % b peut en avoir un différent. Cela peut être utile, par exemple, lors de l'implémentation de listes cycliques :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | fun getNextIndexCyclic(current: Int, size: Int ) = (current + 1).mod(size) fun getPreviousIndexCyclic(current: Int, size: Int ) = (current - 1).mod(size) // unlike %, mod() produces the expected non-negative value even if (current - 1) is less than 0 val size = 5 for (i in 0..(size * 2)) print(getNextIndexCyclic(i, size)) println() for (i in 0..(size * 2)) print(getPreviousIndexCyclic(i, size)) |
Collections : firstNotNullOf() et firstNotNullOfOrNull()
L'API des collections Kotlin couvre une gamme d'opérations populaires sur les collections avec des fonctions intégrées. Pour les cas qui ne sont pas courants, vous combinez généralement les appels de ces fonctions. Cela fonctionne, mais cela n'a pas toujours l'air très élégant et peut causer un overhead.
Par exemple, pour obtenir le premier résultat non nul d'une fonction de sélection sur les éléments de la collection, vous pouvez appeler mapNotNull() et first(). Dans la version 1.5.0, vous pouvez le faire en un seul appel d'une nouvelle fonction firstNotNullOf(). Avec firstNotNullOf(), JetBrains a ajouté son équivalent *orNull() qui produit null s'il n'y a pas de valeur à renvoyer.
Voici un exemple de la façon dont cela peut raccourcir votre code. Supposons que vous ayez une classe avec une propriété Nullable et que vous ayez besoin de sa première valeur non null dans une liste des instances de classe.
Code : | Sélectionner tout |
1 2 | class Item(val name: String?) |
Vous pouvez l'implémenter en itérant la collection et en vérifiant si une propriété n'est pas null :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | // Option 1: manual implementation for (element in collection) { val itemName = element.name if (itemName != null) return itemName } return null |
Une autre méthode consiste à utiliser les fonctions mapNotNull() et firstOrNull() existantes précédemment. Notez que mapNotNull() construit une collection intermédiaire, qui nécessite de la mémoire supplémentaire, en particulier pour les grandes collections. Et donc, une transformation en séquence peut également être nécessaire ici.
Code : | Sélectionner tout |
1 2 3 4 5 6 | // Option 2: old stdlib functions return collection // .asSequence() // Avoid creating intermediate list for big collections .mapNotNull { it.name } .firstOrNull() |
Et voici à quoi cela ressemble avec la nouvelle fonction :
Code : | Sélectionner tout |
1 2 3 | // Option 3: new firstNotNullOfOrNull() return collection.firstNotNullOfOrNull { it.name } |
Modifications de la bibliothèque de test
JetBrains n'a pas fourni de mises à jour majeures de la bibliothèque de test Kotlin kotlin-test depuis plusieurs versions, mais l'entreprise apporte maintenant des changements attendus depuis longtemps. Avec la version 1.5.0-RC, vous pouvez essayer un certain nombre de nouvelles fonctionnalités :
- dépendance kotlin-test unique dans les projets multiplateformes ;
- choix automatique d'un framework de test pour les sources Kotlin/JVM ;
- mises à jour de la fonction d'assertion.
Dépendance kotlin-test dans les projets multiplateformes
JetBrains poursuit le développement du processus de configuration des projets multiplateformes. Dans la version 1.5.0, l'entreprise a simplifié la configuration d'une dépendance sur kotlin-test pour toutes les sources. Maintenant, la dépendance kotlin-test dans l'ensemble de sources de test commun est la seule que vous devez ajouter. Le plugin Gradle déduira la dépendance de plateforme correspondante pour d'autres ensembles de sources :
- kotlin-test-junit pour les ensembles de sources JVM. Vous pouvez également passer à kotlin-test-junit-5 ou kotlin-test-testng si vous les activez explicitement ;
- kotlin-test-js pour les ensembles de sources Kotlin/JS ;
- kotlin-test-common et kotlin-test-annotations-common pour les ensembles de sources communs ;
- aucun artefact supplémentaire pour les ensembles de sources Kotlin/Native car Kotlin/Native fournit des implémentations intégrées de l'API kotlin-test.
Choix automatique d'un framework de test pour les ensembles de sources Kotlin/JVM
Une fois que vous avez spécifié la dépendance kotlin-test dans l'ensemble de sources de test commun comme décrit ci-dessus, les ensembles de sources JVM reçoivent automatiquement la dépendance sur JUnit 4. C'est tout. Vous pouvez aussitôt écrire et exécuter des tests. Voici à quoi cela ressemble dans le DSL Groovy :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | kotlin { sourceSets { commonTest { dependencies { // This brings the dependency // on JUnit 4 transitively implementation kotlin('test') } } } } |
Et dans le DSL Kotlin, c'est :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | kotlin { sourceSets { val commonTest by getting { dependencies { // This brings the dependency // on JUnit 4 transitively implementation(kotlin("test")) } } } } |
Vous pouvez également basculer vers JUnit 5 ou TestNG en appelant simplement une fonction dans la tâche de test : useJUnitPlatform() ou useTestNG().
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | kotlin { jvm { testRuns["test"].executionTask.configure { // enable TestNG support useTestNG() // or // enable JUnit Platform (a.k.a. JUnit 5) support useJUnitPlatform() } } } |
La même chose fonctionne dans les projets uniquement JVM lorsque vous ajoutez la dépendance kotlin-test.
Mises à jour des fonctions d'assertion
Pour la version 1.5.0, JetBrains a préparé un certain nombre de nouvelles fonctions d'assertion ainsi que des améliorations aux fonctions existantes. Tout d'abord, jetons un coup d'œil aux nouvelles fonctions :
- assertIs<T>() et assertIsNot<T>() vérifient le type de la valeur ;
- assertContentEquals() compare le contenu du conteneur pour les tableaux, les séquences et tout Iterable. Plus précisément, il vérifie si les attendus et réels contiennent les mêmes éléments dans le même ordre ;
- assertEquals() et assertNotEquals() pour Double et Float ont de nouvelles surcharges avec un troisième paramètre - la précision ;
- assertContains() vérifie la présence d'un élément dans tout objet avec l'opérateur contains() défini : tableau, liste, plage, etc.
Voici un bref exemple qui montre l'utilisation de ces fonctions :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Test fun test() { val expectedArray = arrayOf(1, 2, 3) val actualArray = Array(3) { it + 1 } val first: Any = actualArray[0] assertIs<Int>(first) // first is smart-cast to Int now println("${first + 1}") assertContentEquals(expectedArray, actualArray) assertContains(expectedArray, 2) val x = sin(PI) // precision parameter val tolerance = 0.000001 assertEquals(0.0, x, tolerance) } |
Comment utiliser toutes les fonctionnalités de Kotlin 1.5.0
Vous pouvez utiliser toutes ces API Kotlin modernes dans vos projets réels, en installant le plugin Kotlin 1.5.0-RC dans IntelliJ IDEA ou Android Studio. Vous trouverez ici les instructions pour l'installation du plugin Kotlin 1.5.0-RC dans votre EDI préféré.
Changements dans Kotlin 1.5.0-RC pour les bibliothèques standard et de test
Téléchargez la dernière version d'IntelliJ IDEA fournie avec le plugin Kotlin 2021.1