Guide du bloc "Quand \ {}" dans Kotlin

Guide du bloc «quand \ {}» dans Kotlin

1. introduction

Ce tutoriel présente le blocwhen\{} en langage Kotlin et montre les différentes manières dont il peut être utilisé.

Pour comprendre le contenu de cet article, une connaissance de base de la langue kotlin est nécessaire. Vous pouvez consulter l'article deintroduction to the Kotlin Language sur l'exemple pour en savoir plus sur la langue.

2. Blocwhen\{} de Kotlin

Le blocWhen\{} est essentiellement une forme avancée de l'instructionswitch-case connue de Java.

Dans Kotlin, si un cas correspondant est trouvé, seul le code du bloc de cas respectif est exécuté et l'exécution se poursuit avec l'instruction suivante après le blocwhen. Cela signifie essentiellement qu'aucune instruction break n'est nécessaire à la fin de chaque bloccase.

Pour illustrer l'utilisation dewhen\{}, définissons une classe enum qui contient la première lettre dans le champ des permissions pour certains des types de fichiers sous Unix:

enum class UnixFileType {
    D, HYPHEN_MINUS, L
}

Définissons également une hiérarchie de classes qui modélisent les types de fichiers Unix respectifs:

sealed class UnixFile {

    abstract fun getFileType(): UnixFileType

    class RegularFile(val content: String) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.HYPHEN_MINUS
        }
    }

    class Directory(val children: List) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.D
        }
    }

    class SymbolicLink(val originalFile: UnixFile) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.L
        }
    }
}

2.1. When\{} en tant qu'expression

Une grande différence par rapport à l'instruction switch de Java est quethe when\{} block in Kotlin can be used both as a statement and as an expression. Kotlin suit les principes des autres langages fonctionnels et les structures de contrôle de flux sont des expressions et le résultat de leur évaluation peut être renvoyé à l'appelant.

Si la valeur renvoyée est affectée à une variable, le compilateur vérifiera que le type de la valeur renvoyée est compatible avec le type attendu par le client et nous en informera dans le cas contraire:

@Test
fun testWhenExpression() {
    val directoryType = UnixFileType.D

    val objectType = when (directoryType) {
        UnixFileType.D -> "d"
        UnixFileType.HYPHEN_MINUS -> "-"
        UnixFileType.L -> "l"
    }

    assertEquals("d", objectType)
}

Il y a deux choses à noter lors de l'utilisation de when comme expression dans Kotlin.

Premièrement, la valeur renvoyée à l'appelant est la valeur du bloc de casse correspondant ou, en d'autres termes, la dernière valeur définie dans le bloc.

La deuxième chose à noter est que nous devons garantir à l'appelant une valeur. Pour ce faire, nous devons nous assurer que les cas, dans le bloc when, couvrent toutes les valeurs possibles pouvant être affectées à l'argument.

2.2. When\{} comme expression avec cas par défaut

Un cas par défaut correspondra à toute valeur d'argument qui ne correspond pas à un cas normal et dans Kotlin est déclaré à l'aide de la clauseelse. Dans tous les cas, le compilateur Kotlin supposera que toutes les valeurs d'argument possibles sont couvertes par le bloc when et se plaindra au cas où ce ne serait pas le cas.

Pour ajouter un cas par défaut dans l'expressionwhen de Kotlin:

@Test
fun testWhenExpressionWithDefaultCase() {
    val fileType = UnixFileType.L

    val result = when (fileType) {
        UnixFileType.L -> "linking to another file"
        else -> "not a link"
    }

    assertEquals("linking to another file", result)
}

2.3. When\{} Expression with a Case That Throws an Exception

Dans Kotlin,throw renvoie une valeur de typeNothing.

Dans ce cas,Nothing est utilisé pour déclarer que l'expression n'a pas pu calculer une valeur. Nothing est le type qui hérite de tous les types définis par l'utilisateur et intégrés dans Kotlin.

Par conséquent, puisque le type est compatible avec n'importe quel argument que nous utiliserions dans un blocwhen, il est parfaitement valide de lever une exception à partir d'uncase même si le blocwhen est utilisé comme une expression.

Définissons une expression when où l’un des cas lève une exception:

@Test(expected = IllegalArgumentException::class)
fun testWhenExpressionWithThrowException() {
    val fileType = UnixFileType.L

    val result: Boolean = when (fileType) {
        UnixFileType.HYPHEN_MINUS -> true
        else -> throw IllegalArgumentException("Wrong type of file")
    }
}

2.4. When\{} utilisé comme instruction

Nous pouvons également utiliser le blocwhen comme instruction.

Dans ce cas, il n'est pas nécessaire de couvrir toutes les valeurs possibles pour l'argument et la valeur calculée dans chaque cas, le bloc, s'il y en a un, est simplement ignorée. Lorsqu'il est utilisé comme instruction, le blocwhen peut être utilisé de la même manière que l'instructionswitch est utilisée en Java.

Utilisons le blocwhen comme instruction:

@Test
fun testWhenStatement() {
    val fileType = UnixFileType.HYPHEN_MINUS

    when (fileType) {
        UnixFileType.HYPHEN_MINUS -> println("Regular file type")
        UnixFileType.D -> println("Directory file type")
    }
}

Nous pouvons voir à partir de l'exemple qu'il n'est pas obligatoire de couvrir toutes les valeurs d'argument possibles lorsque nous utilisonswhen comme instruction.

2.5. Combinaison de casWhen\{}

L'expressionwhen de Kotlin nous permet de combiner différents cas en un seul en concaténant les conditions de correspondance avec une virgule.

Un seul cas doit correspondre pour que le bloc de code respectif soit exécuté, donc la virgule agit comme un opérateurOR.

Créons un cas qui combine deux conditions:

@Test
fun testCaseCombination() {
    val fileType = UnixFileType.D

    val frequentFileType: Boolean = when (fileType) {
        UnixFileType.HYPHEN_MINUS, UnixFileType.D -> true
        else -> false
    }

    assertTrue(frequentFileType)
}

2.6. When\{} utilisé sans argument

Kotlin nous permet d'omettre la valeur de l'argument dans le blocwhen.

Cela tourne essentiellement quand il s'agit d'une simple expressionif-elseif qui vérifie séquentiellement les cas et exécute le bloc de code du premier cas correspondant. Si nous omettons l'argument dans le bloc when, les expressions de cas doivent alors être considérées comme vraies ou fausses.

Créons un blocwhen qui omet l'argument:

@Test
fun testWhenWithoutArgument() {
    val fileType = UnixFileType.L

    val objectType = when {
        fileType === UnixFileType.L -> "l"
        fileType === UnixFileType.HYPHEN_MINUS -> "-"
        fileType === UnixFileType.D -> "d"
        else -> "unknown file type"
    }

    assertEquals("l", objectType)
}

2.7. Expressions de cas dynamiques

En Java, l'instructionswitch ne peut être utilisée qu'avec les primitives et leurs types encadrés, les énumérations et la classeString. En revanche,Kotlin allows us to use the when block with any built-in or user defined type.

De plus, il n'est pas nécessaire que les observations soient des expressions constantes, comme en Java. Les observations dans Kotlin peuvent être des expressions dynamiques évaluées lors de l'exécution. Par exemple, les observations peuvent être le résultat d'une fonction tant que le type de retour de la fonction est compatible avec le type de l'argument de blocwhen.

Définissons un blocwhen avec des expressions de cas dynamiques:

@Test
fun testDynamicCaseExpression() {
    val unixFile = UnixFile.SymbolicLink(UnixFile.RegularFile("Content"))

    when {
        unixFile.getFileType() == UnixFileType.D -> println("It's a directory!")
        unixFile.getFileType() == UnixFileType.HYPHEN_MINUS -> println("It's a regular file!")
        unixFile.getFileType() == UnixFileType.L -> println("It's a soft link!")
    }
}

2.8. Expressions de cas de gamme et de collection

Il est possible de définir un cas dans un blocwhen qui vérifie si une collection donnée ou une plage de valeurs contient l'argument.

Pour cette raison, Kotlin fournit l'opérateurin qui est un sucre syntaxique pour la méthodecontains(). Cela signifie que Kotlin en coulisses traduit l'élément casein en collection.contains (élément).

Pour vérifier si l'argument est dans une liste:

@Test
fun testCollectionCaseExpressions() {
    val regularFile = UnixFile.RegularFile("Test Content")
    val symbolicLink = UnixFile.SymbolicLink(regularFile)
    val directory = UnixFile.Directory(listOf(regularFile, symbolicLink))

    val isRegularFileInDirectory = when (regularFile) {
        in directory.children -> true
        else -> false
    }

    val isSymbolicLinkInDirectory = when {
        symbolicLink in directory.children -> true
        else -> false
    }

    assertTrue(isRegularFileInDirectory)
    assertTrue(isSymbolicLinkInDirectory)
}

Pour vérifier que l'argument est dans une plage:

@Test
fun testRangeCaseExpressions() {
    val fileType = UnixFileType.HYPHEN_MINUS

    val isCorrectType = when (fileType) {
        in UnixFileType.D..UnixFileType.L -> true
        else -> false
    }

    assertTrue(isCorrectType)
}

Même si le typeREGULAR_FILE n'est pas explicitement contenu dans la plage, son ordinal est entre les ordinaux deDIRECTORY etSYMBOLIC_LINK et donc le test est réussi.

2.9. Opérateur de casIs et Smart Cast

Nous pouvons utiliser l'opérateuris de Kotlin pour vérifier si l'argument est une instance d'un type spécifié. L'opérateuris est similaire à l'opérateurinstanceof en Java.

Cependant, Kotlin nous fournit une fonctionnalité appelée «distribution intelligente». Après avoir vérifié si l'argument est une instance d'un type donné, il n'est pas nécessaire de convertir explicitement l'argument en ce type, car le compilateur le fait pour nous.

Par conséquent, nous pouvons utiliser les méthodes et les propriétés définies dans le type donné directement dans le bloc de cas. Pour utiliser l'opérateur is avec la fonction «smart cast» dans un blocwhen:

@Test
fun testWhenWithIsOperatorWithSmartCase() {
    val unixFile: UnixFile = UnixFile.RegularFile("Test Content")

    val result = when (unixFile) {
        is UnixFile.RegularFile -> unixFile.content
        is UnixFile.Directory -> unixFile.children.map { it.getFileType() }.joinToString(", ")
        is UnixFile.SymbolicLink -> unixFile.originalFile.getFileType()
    }

    assertEquals("Test Content", result)
}

Sans transtyper explicitementunixFile enRegularFile,Directory ouSymbolicLink, nous avons pu utiliserRegularFile.content,Directory.children etSymbolicLink.originalFile respectivement.

3. Conclusion

Dans cet article, nous avons vu plusieurs exemples d'utilisation du blocwhen offert par le langage Kotlin.

Même s'il n'est pas possible de faire une correspondance de motifs en utilisantwhen dans Kotlin, comme c'est le cas avec les structures correspondantes dans Scala et d'autres langages JVM, le blocwhen est suffisamment polyvalent pour nous faire totalement oublier ces derniers. Caractéristiques.

L'implémentation complète des exemples de cet article peut être trouvéeover on GitHub.