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.