O que é when em Kotlin?

Resposta rápida: when é a estrutura do Kotlin usada para escolher entre vários casos. Ela substitui o switch de outras linguagens, mas também compara ranges com in, verifica tipos com is, agrupa valores e aceita condições booleanas. Como é uma expressão, o when pode retornar diretamente o valor de uma variável ou função. Quando o compilador não consegue garantir que todos os casos foram tratados, inclua else.

val mensagem = when (status) {
    200 -> "Sucesso"
    in 400..499 -> "Erro do cliente"
    in 500..599 -> "Erro do servidor"
    else -> "Status desconhecido"
}

O when funciona tanto como expressão, retornando um valor, quanto para apenas executar um bloco. Ele é mais flexível que o switch tradicional de Java ou C porque suporta valores, ranges, testes de tipo, enums, sealed classes e condições compostas.

NecessidadeForma recomendada
Comparar valores exatoswhen (valor) { 1 -> ... }
Agrupar opções"a", "e", "i" -> ...
Testar um intervaloin 1..10 -> ...
Verificar um tipois String -> ...
Avaliar condições diferenteswhen { condição -> ... }
Modelar estados fechadoswhen com enum ou sealed class

Sintaxe básica

fun classificarNota(nota: Int): String {
    return when (nota) {
        10 -> "Perfeito!"
        in 7..9 -> "Aprovado"
        in 5..6 -> "Recuperação"
        in 0..4 -> "Reprovado"
        else -> "Nota inválida"
    }
}

fun main() {
    println(classificarNota(8))  // Aprovado
    println(classificarNota(3))  // Reprovado
}

When como expressão

Quando when é usado como expressão (retornando valor), o else é obrigatório — a menos que o compilador consiga verificar que todos os casos estão cobertos:

enum class DiaSemana { SEG, TER, QUA, QUI, SEX, SAB, DOM }

fun tipoDeDia(dia: DiaSemana) = when (dia) {
    DiaSemana.SEG, DiaSemana.TER, DiaSemana.QUA,
    DiaSemana.QUI, DiaSemana.SEX -> "Dia útil"
    DiaSemana.SAB, DiaSemana.DOM -> "Fim de semana"
}

Verificação de tipo com is

fun descrever(obj: Any): String = when (obj) {
    is String -> "Texto com ${obj.length} caracteres"
    is Int -> "Número inteiro: $obj"
    is List<*> -> "Lista com ${obj.size} itens"
    is Boolean -> if (obj) "Verdadeiro" else "Falso"
    else -> "Tipo desconhecido"
}

fun main() {
    println(descrever("Kotlin"))        // Texto com 6 caracteres
    println(descrever(42))              // Número inteiro: 42
    println(descrever(listOf(1, 2, 3))) // Lista com 3 itens
}

Repare que dentro de cada branch, o smart cast já aplica o tipo automaticamente.

When sem argumento

Você pode usar when sem argumento, funcionando como uma cadeia de if-else:

fun avaliarTemperatura(temp: Double): String = when {
    temp < 0 -> "Congelante!"
    temp < 15 -> "Tá frio, hein"
    temp < 25 -> "Temperatura agradável"
    temp < 35 -> "Tá quente"
    else -> "Derretendo!"
}

fun main() {
    println(avaliarTemperatura(28.0)) // Tá quente
}

When com sealed class

A combinação com sealed class é perfeita — o compilador garante que todos os casos são tratados, sem precisar de else. Essa é uma das duplas mais poderosas do Kotlin pra modelar estados e resultados.

sealed class Resultado {
    data class Sucesso(val dados: String) : Resultado()
    data class Erro(val mensagem: String) : Resultado()
    data object Carregando : Resultado()
}

fun tratarResultado(resultado: Resultado): String = when (resultado) {
    is Resultado.Sucesso -> "Dados recebidos: ${resultado.dados}"
    is Resultado.Erro -> "Falha: ${resultado.mensagem}"
    is Resultado.Carregando -> "Aguarde, carregando..."
}

fun main() {
    val r1 = Resultado.Sucesso("Lista de usuários")
    val r2 = Resultado.Erro("Timeout na conexão")
    val r3 = Resultado.Carregando

    println(tratarResultado(r1)) // Dados recebidos: Lista de usuários
    println(tratarResultado(r2)) // Falha: Timeout na conexão
    println(tratarResultado(r3)) // Aguarde, carregando...
}

Como Resultado é uma sealed class, o compilador sabe que existem exatamente três subtipos. Se você adicionar um novo subtipo no futuro, o compilador vai avisar em todos os when que ficaram incompletos.

When com múltiplas condições

Quando cada branch depende de uma condição booleana diferente, use when sem argumento. Nesse formato, os branches são avaliados de cima para baixo, como uma cadeia organizada de if e else if:

fun classificarIdade(idade: Int, temDocumento: Boolean): String = when {
    idade < 0 -> "Idade inválida"
    idade < 12 -> "Criança"
    idade < 18 && temDocumento -> "Adolescente com documento"
    idade < 18 -> "Adolescente sem documento"
    idade < 60 -> "Adulto"
    else -> "Idoso"
}

fun main() {
    println(classificarIdade(15, true))  // Adolescente com documento
    println(classificarIdade(15, false)) // Adolescente sem documento
    println(classificarIdade(45, true))  // Adulto
}

Isso não é uma sintaxe especial de guarda: é o when sem valor de entrada. A ordem importa, porque a primeira condição verdadeira vence. Coloque condições mais específicas antes das mais gerais.

Casos de Uso no Mundo Real

  • Tratamento de respostas HTTP: O when é ideal para tratar diferentes códigos de status HTTP. Você pode agrupar ranges como in 200..299 para sucesso, in 400..499 para erros do cliente e in 500..599 para erros do servidor, tornando o código muito mais legível do que uma cadeia de if-else.
  • Navegação em aplicativos Android: Em apps com Jetpack Compose ou Fragments, o when combinado com sealed classes é o padrão para decidir qual tela exibir com base no estado atual de navegação.
  • Parsing de formatos de dados: Ao processar dados de diferentes formatos (JSON, XML, CSV), o when com verificação de tipo (is) permite tratar cada formato de maneira específica com smart cast automático.
  • Máquinas de estado: Sistemas como processamento de pedidos (novo, pago, enviado, entregue, cancelado) se beneficiam enormemente do when com sealed classes, garantindo que toda transição de estado é tratada explicitamente.

Boas Práticas

  • Prefira when como expressão: Sempre que possível, use when retornando um valor diretamente com val resultado = when { ... }. Isso elimina a necessidade de variáveis mutáveis e torna o código mais funcional.
  • Use sealed classes em vez de else: Quando você tem um conjunto fixo de tipos ou estados, modele-os com sealed classes. O compilador vai garantir que todos os casos sejam tratados, e qualquer novo caso adicionado no futuro será detectado em tempo de compilação.
  • Agrupe valores relacionados: O when permite agrupar múltiplos valores na mesma branch usando vírgula, como 'a', 'e', 'i', 'o', 'u' -> "vogal". Use isso para evitar duplicação de lógica.
  • Prefira when a cadeias longas de if-else: Quando você tem três ou mais condições sobre o mesmo valor, when é quase sempre mais legível e menos propenso a erros do que if-else if-else.
  • Aproveite o smart cast: Dentro de branches com is, o Kotlin já faz o cast automaticamente. Não use as manualmente após verificar o tipo com is — isso é redundante e polui o código.

Erros Comuns

  • Esquecer o else em expressões when: Quando when é usado como expressão (retornando valor), o else é obrigatório se os casos não são exaustivos. O compilador vai apontar o erro, mas é comum esquecer durante a escrita inicial.
  • Ordem errada dos branches: O when avalia os branches de cima para baixo e executa o primeiro que corresponder. Se você colocar um branch mais genérico antes de um mais específico, o específico nunca será alcançado. Por exemplo, is Number antes de is Int faz com que inteiros caiam sempre no branch de Number.
  • Não aproveitar ranges: Escrever 1 -> ..., 2 -> ..., 3 -> ... quando todos retornam o mesmo valor é desnecessário. Use in 1..3 -> ... para simplificar. O mesmo vale para verificações como "a", "b", "c" -> ....
  • Usar when para apenas dois casos: Se você tem apenas duas opções (verdadeiro/falso, presente/ausente), um simples if-else é mais claro e conciso do que um when. Use when quando há três ou mais caminhos.
  • Não tratar todos os casos de um enum: Mesmo quando o when é usado como instrução (sem retornar valor), é boa prática tratar todos os valores do enum. Isso evita bugs silenciosos quando novos valores são adicionados ao enum no futuro.

Perguntas Frequentes

when é o equivalente ao switch? Sim. Para quem migra de Java, JavaScript, C ou C#, when cumpre o papel de selecionar entre vários casos, mas também oferece ranges com in, testes de tipo com is, condições arbitrárias sem argumento e retorno de valor. Em Java moderno, switch também pode ser usado como expressão; ainda assim, a sintaxe e a integração de when com smart casts e tipos selados são próprias do Kotlin.

Posso usar when sem chaves nas branches? Sim. Se a branch tem apenas uma expressão, não precisa de chaves: 10 -> "dez". Mas se precisa de múltiplas instruções, envolva com chaves e a última expressão será o valor de retorno da branch.

O when é mais performático que if-else? Depende do caso. Para comparações com valores constantes de Int ou String, o compilador pode gerar um tableswitch ou lookupswitch no bytecode, que é mais eficiente que uma série de comparações. Para condições complexas ou when sem argumento, a performance é equivalente a if-else.

Como funciona o when com valor de retorno em blocos? Quando uma branch do when usa chaves para múltiplas instruções, a última expressão do bloco é o valor retornado. Por exemplo: in 1..10 -> { println("Processando"); "válido" } retorna "válido".

Termos Relacionados

  • Sealed Class — classes seladas que combinam com when para garantir exaustividade.
  • Enum — enumerações que permitem um when exaustivo sem else.
  • val — variáveis somente leitura, ideais para receber o resultado de uma expressão when.
  • Smart Cast — mecanismo que permite usar o tipo verificado com is dentro dos branches.
  • Estruturas condicionais em Kotlin — tutorial para praticar if, else e when em conjunto.