Guia para o bloco "quando \ {}" no Kotlin

Guia para o bloco "quando \ {}" no Kotlin

1. Introdução

Este tutorial apresenta o blocowhen\{} na linguagem Kotlin e demonstra as várias maneiras como ele pode ser usado.

Para entender o material deste artigo, é necessário conhecimento básico da linguagem Kotlin. Você pode dar uma olhada no artigointroduction to the Kotlin Language sobre um exemplo para aprender mais sobre o idioma.

2. Bloco dewhen\{} de Kotlin

O blocoWhen\{} é essencialmente uma forma avançada da instruçãoswitch-case conhecida em Java.

Em Kotlin, se um caso correspondente for encontrado, apenas o código no respectivo bloco de caso é executado e a execução continua com a próxima instrução após o blocowhen. Isso significa essencialmente que nenhuma instrução break é necessária no final de cada blococase.

Para demonstrar o uso dewhen\{}, vamos definir uma classe enum que contém a primeira letra no campo de permissões para alguns dos tipos de arquivo no Unix:

enum class UnixFileType {
    D, HYPHEN_MINUS, L
}

Vamos também definir uma hierarquia de classes que modelam os respectivos tipos de arquivo Unix:

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\{} como uma expressão

Uma grande diferença da instrução switch do Java é quethe when\{} block in Kotlin can be used both as a statement and as an expression. Kotlin segue os princípios de outras linguagens funcionais e as estruturas de controle de fluxo são expressões e o resultado de sua avaliação pode ser retornado ao chamador.

Se o valor retornado for atribuído a uma variável, o compilador verificará se o tipo de valor retornado é compatível com o tipo esperado pelo cliente e nos informará caso não seja:

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

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

    assertEquals("d", objectType)
}

Há duas coisas a serem observadas ao usar when como uma expressão no Kotlin.

Primeiro, o valor retornado ao chamador é o valor do bloco de maiúsculas e minúsculas correspondente ou, em outras palavras, o último valor definido no bloco.

A segunda coisa a notar é que precisamos garantir que o chamador obtenha um valor. Para que isso aconteça, precisamos garantir que os casos, no bloco when, abranjam todos os valores possíveis que possam ser atribuídos ao argumento.

2.2. When\{} como uma expressão com caixa padrão

Um caso padrão corresponderá a qualquer valor de argumento que não seja correspondido por um caso normal e em Kotlin seja declarado usando a cláusulaelse. De qualquer forma, o compilador Kotlin assumirá que todo valor possível de argumento é coberto pelo bloco when e reclamará caso não o seja.

Para adicionar um caso padrão na expressãowhen 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

Em Kotlin,throw retorna um valor do tipoNothing.

Nesse caso,Nothing é usado para declarar que a expressão falhou ao calcular um valor. Nothing é o tipo que herda de todos os tipos definidos pelo usuário e integrados no Kotlin.

Portanto, uma vez que o tipo é compatível com qualquer argumento que usaríamos em um blocowhen, é perfeitamente válido lançar uma exceção de umcase, mesmo se o blocowhen for usado como uma expressão.

Vamos definir uma expressão when onde um dos casos lança uma exceção:

@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\{} usado como uma declaração

Também podemos usar o blocowhen como uma declaração.

Nesse caso, não precisamos cobrir todos os valores possíveis para o argumento e o valor calculado em cada bloco de caso, se houver, é apenas ignorado. Quando usado como uma instrução, o blocowhen pode ser usado da mesma forma que a instruçãoswitch é usada em Java.

Vamos usar o blocowhen como uma declaração:

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

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

Podemos ver pelo exemplo que não é obrigatório cobrir todos os valores de argumento possíveis quando estamos usandowhen como uma instrução.

2.5. CombinandoWhen\{} Casos

A expressãowhen de Kotlin nos permite combinar diferentes casos em um, concatenando as condições de correspondência com uma vírgula.

Apenas um caso deve corresponder para o respectivo bloco de código a ser executado, então a vírgula atua como um operadorOR.

Vamos criar um caso que combina duas condições:

@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\{} usado sem um argumento

Kotlin nos permite omitir o valor do argumento no blocowhen.

Isso basicamente se transforma quando em uma expressãoif-elseif simples que verifica os casos sequencialmente e executa o bloco de código do primeiro caso correspondente. Se omitirmos o argumento no bloco when, as expressões de caso deverão ser avaliadas como true ou false.

Vamos criar um blocowhen que omite o argumento:

@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. Expressões Dinâmicas de Caso

Em Java, a instruçãoswitch só pode ser usada com primitivas e seus tipos em caixa, enums e a classeString. Em contraste,Kotlin allows us to use the when block with any built-in or user defined type.

Além disso, não é necessário que os casos sejam expressões constantes como em Java. Casos no Kotlin podem ser expressões dinâmicas avaliadas em tempo de execução. Por exemplo, casos podem ser o resultado de uma função, desde que o tipo de retorno da função seja compatível com o tipo do argumento de blocowhen.

Vamos definir um blocowhen com expressões de caso dinâmicas:

@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. Expressões de caso de coleção e intervalo

É possível definir um caso em um blocowhen que verifica se uma determinada coleção ou um intervalo de valores contém o argumento.

Por esse motivo, Kotlin fornece o operadorin, que é um açúcar sintático para o métodocontains(). Isso significa que o Kotlin nos bastidores traduz o elemento de casoin para collection.contains (element).

Para verificar se o argumento está em uma lista:

@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)
}

Para verificar se o argumento está em um intervalo:

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

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

    assertTrue(isCorrectType)
}

Mesmo que o tipoREGULAR_FILE não esteja explicitamente contido no intervalo, seu ordinal está entre os ordinais deDIRECTORYeSYMBOLIC_LINKe, portanto, o teste foi bem-sucedido.

2.9. Is Operador de caso e elenco inteligente

Podemos usar o operadoris de Kotlin para verificar se o argumento é uma instância de um tipo especificado. O operadoris é semelhante ao operadorinstanceof em Java.

No entanto, Kotlin nos fornece um recurso chamado "elenco inteligente". Depois de verificarmos se o argumento é uma instância de um determinado tipo, não precisamos converter explicitamente o argumento para esse tipo, pois o compilador faz isso por nós.

Portanto, podemos usar os métodos e propriedades definidos no tipo especificado diretamente no bloco do caso. Para usar o operador is com o recurso "smart cast" em um blocowhen:

@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)
}

Sem converter explicitamenteunixFile emRegularFile,Directory ouSymbolicLink, pudemos usarRegularFile.content,Directory.children eSymbolicLink.originalFile respectivamente.

3. Conclusão

Neste artigo, vimos vários exemplos de como usar o blocowhen oferecido pela linguagem Kotlin.

Mesmo que não seja possível fazer correspondência de padrões usandowhen no Kotlin, como é o caso com as estruturas correspondentes em Scala e outras linguagens JVM, o blocowhen é versátil o suficiente para nos fazer esquecer totalmente deles recursos.

A implementação completa dos exemplos para este artigo pode ser encontradaover on GitHub.