O que é Sealed Class em Kotlin?
Uma sealed class em Kotlin define uma hierarquia restrita de tipos. Isso significa que todas as subclasses possíveis são conhecidas em tempo de compilação. É como um enum turbinado — cada subtipo pode ter propriedades e comportamentos diferentes.
A grande vantagem é que, ao usar when, o compilador sabe exatamente quais são os casos possíveis e avisa se você esquecer algum.
Sintaxe básica
sealed class Resultado {
data class Sucesso(val dados: String) : Resultado()
data class Erro(val mensagem: String, val codigo: Int) : Resultado()
object Carregando : Resultado()
}
Cada subclasse pode ter seus próprios campos e lógica. Diferente de um enum, onde cada valor é uma instância única, aqui cada subtipo pode carregar dados distintos.
Usando com when
A mágica acontece quando você combina sealed class com when:
fun tratarResultado(resultado: Resultado): String {
return when (resultado) {
is Resultado.Sucesso -> "Deu certo: ${resultado.dados}"
is Resultado.Erro -> "Erro ${resultado.codigo}: ${resultado.mensagem}"
is Resultado.Carregando -> "Aguarde..."
}
}
Repare que não precisa de else. O compilador sabe que esses são todos os casos possíveis. Se amanhã você adicionar um novo subtipo, o compilador vai apontar todos os lugares onde falta tratar o novo caso.
Exemplo prático com estado de tela
sealed class EstadoTela {
object Inicial : EstadoTela()
data class Conteudo(val itens: List<String>) : EstadoTela()
data class FalhaConexao(val tentativas: Int) : EstadoTela()
}
fun renderizar(estado: EstadoTela) {
when (estado) {
is EstadoTela.Inicial -> println("Carregando tela inicial...")
is EstadoTela.Conteudo -> println("Exibindo ${estado.itens.size} itens")
is EstadoTela.FalhaConexao -> println("Falha! Tentativa ${estado.tentativas}")
}
}
Sealed class com comportamento
As subclasses de uma sealed class podem ter métodos próprios e até implementar interfaces diferentes:
sealed class Pagamento {
abstract fun processar(): String
data class CartaoCredito(
val numero: String,
val parcelas: Int
) : Pagamento() {
override fun processar(): String {
val ultimosDigitos = numero.takeLast(4)
return "Cartão ****$ultimosDigitos em ${parcelas}x"
}
}
data class Pix(val chave: String) : Pagamento() {
override fun processar(): String {
return "PIX para chave $chave"
}
}
data class Boleto(val codigoBarras: String, val vencimento: String) : Pagamento() {
override fun processar(): String {
return "Boleto com vencimento em $vencimento"
}
}
object Dinheiro : Pagamento() {
override fun processar() = "Pagamento em dinheiro"
}
}
fun finalizarCompra(pagamento: Pagamento) {
println("Processando: ${pagamento.processar()}")
}
fun main() {
finalizarCompra(Pagamento.CartaoCredito("1234567890123456", 3))
finalizarCompra(Pagamento.Pix("karina@email.com"))
finalizarCompra(Pagamento.Boleto("12345.67890", "2026-04-15"))
finalizarCompra(Pagamento.Dinheiro)
}
Sealed interface (Kotlin 1.5+)
A partir do Kotlin 1.5, você pode usar sealed interface para criar hierarquias seladas com mais flexibilidade, permitindo que uma classe implemente múltiplas sealed interfaces:
sealed interface Erro {
val mensagem: String
}
sealed interface Recuperavel {
fun tentar(): Boolean
}
data class ErroDeRede(
override val mensagem: String,
val codigo: Int
) : Erro, Recuperavel {
override fun tentar(): Boolean {
println("Tentando reconexão...")
return codigo != 404
}
}
data class ErroDeValidacao(
override val mensagem: String,
val campo: String
) : Erro {
// Não é recuperável
}
fun tratarErro(erro: Erro) {
println("Erro: ${erro.mensagem}")
if (erro is Recuperavel) {
val sucesso = erro.tentar()
println("Recuperação: ${if (sucesso) "OK" else "falhou"}")
}
}
Quando usar?
Sealed classes são ideais para modelar estados finitos: resultados de operações, estados de UI, eventos de navegação e respostas de rede. Sempre que você tiver um conjunto fechado de possibilidades, pense em sealed class.
Casos de Uso no Mundo Real
- Gerenciamento de estado de UI: em aplicações Android com MVVM ou MVI, sealed classes representam os diferentes estados da tela (carregando, sucesso, erro), garantindo que todos os estados sejam tratados na camada de apresentação.
- Resultado de operações de rede: modelar respostas HTTP como
Sucesso(dados),Erro(código, mensagem)eSemConexao, forçando o tratamento de todos os cenários possíveis. - Eventos de navegação: representar destinos e ações de navegação como subtipos selados, garantindo que rotas novas sejam tratadas em todo o código de navegação.
- Processamento de comandos: em arquiteturas orientadas a eventos, sealed classes representam os diferentes comandos que um sistema pode receber, com o compilador garantindo que nenhum comando fique sem tratamento.
Boas Práticas
- Use
objectpara subtipos sem dados (comoCarregandoouVazio) edata classpara subtipos que carregam informações. Isso aproveita ao máximo os recursos do Kotlin. - Prefira
sealed interfacequando precisar que uma classe participe de múltiplas hierarquias seladas. Sealed interfaces são mais flexíveis que sealed classes. - Evite o branch
elseem expressõeswhencom sealed classes. Oelseesconde a adição de novos subtipos, anulando a principal vantagem da sealed class. - Mantenha a hierarquia selada no mesmo arquivo ou pacote (desde Kotlin 1.5, subclasses podem estar no mesmo pacote). Isso facilita a visualização de todos os casos possíveis.
- Use sealed classes para modelar domínio, não para lógica de infraestrutura. Elas brilham quando representam conceitos do negócio com um conjunto finito de variações.
Erros Comuns
- Adicionar
elsenowhen: ao usarelse, o compilador não avisa quando um novo subtipo é adicionado. Isso elimina a principal vantagem de usar sealed class e pode causar bugs silenciosos. - Criar hierarquias muito profundas: sealed classes dentro de sealed classes criam complexidade desnecessária. Mantenha a hierarquia plana sempre que possível.
- Confundir sealed class com enum: enums são para valores fixos e simples (como dias da semana). Sealed classes são para tipos com dados e comportamentos diferentes. Usar enum quando precisa de sealed class limita o modelo.
- Esquecer de tratar novos subtipos: ao adicionar um novo subtipo à sealed class, todos os
whensemelsevão parar de compilar. Isso é uma vantagem, não um problema. Trate cada caso conscientemente. - Definir subclasses fora do pacote: subclasses de sealed classes devem estar no mesmo pacote (módulo, até Kotlin 1.4). Definir fora causa erro de compilação e frustra o propósito da restrição.
Perguntas Frequentes
Qual a diferença entre sealed class e enum? Enums têm um número fixo de instâncias, e cada instância é um singleton. Sealed classes permitem múltiplas instâncias de cada subtipo, e cada subtipo pode ter propriedades e construtores diferentes. Use enum para valores simples e sealed class para tipos complexos.
Posso herdar de uma sealed class fora do mesmo módulo? Não. Todas as subclasses de uma sealed class devem estar no mesmo módulo de compilação (e no mesmo pacote a partir do Kotlin 1.5). Essa restrição é o que permite ao compilador conhecer todos os subtipos em tempo de compilação.
Sealed class pode ter construtor? Sim. Sealed classes podem ter construtores com parâmetros, e as subclasses precisam chamá-los. Porém, o construtor de uma sealed class é sempre protected por padrão (ou private).
Quando usar sealed interface em vez de sealed class? Use sealed interface quando precisar que um tipo implemente múltiplas hierarquias seladas (herança múltipla de interfaces). Use sealed class quando quiser compartilhar estado ou implementação entre os subtipos.
Termos Relacionados
- Object — usado como subtipo de sealed class quando não há dados associados
- Data Class — o tipo mais comum de subtipo em hierarquias seladas
- Enum — alternativa mais simples para conjuntos fixos de valores
- When — expressão que se beneficia diretamente da exaustividade de sealed classes
- Interface — base para sealed interfaces introduzidas no Kotlin 1.5