---
title: "Sealed Classes em Kotlin: O que São e Como Usar | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/sealed-classes-kotlin/"
markdown_url: "https://kotlin.dev.br/blog/sealed-classes-kotlin.MD"
description: "Aprenda Sealed Classes em Kotlin com exemplos práticos. Entenda como modelar estados e hierarquias de tipos de forma segura e elegante."
date: "2026-03-07"
author: "Karina Melo"
---

# Sealed Classes em Kotlin: O que São e Como Usar | Kotlin Brasil

Aprenda Sealed Classes em Kotlin com exemplos práticos. Entenda como modelar estados e hierarquias de tipos de forma segura e elegante.


Se você já precisou representar um conjunto finito de estados ou tipos no seu código, as Sealed Classes do Kotlin são exatamente o que você procura. Elas combinam o melhor dos enums com a flexibilidade das classes, é o compilador ainda te ajuda a não esquecer nenhum caso. Bora entender!

## O que são Sealed Classes?

Uma sealed class é uma classe abstrata que restringe quais subclasses podem existir. Todas as subclasses devem ser definidas no mesmo pacote (e normalmente no mesmo arquivo). Isso dá ao compilador a garantia de que ele conhece todos os tipos possíveis.

```kotlin
sealed class Resultado {
    data class Sucesso(val dados: String) : Resultado()
    data class Erro(val mensagem: String, val codigo: Int) : Resultado()
    data object Carregando : Resultado()
}
```

## Por que não usar Enum?

Enums são ótimos, mas têm uma limitação: cada valor é uma instância única. Você não pode ter dados diferentes em cada instância:

```kotlin
// Enum — cada valor é fixo
enum class Status { SUCESSO, ERRO, CARREGANDO }
// Como associar uma mensagem de erro ao Status.ERRO? Complicado.

// Sealed Class — cada subclasse pode ter seus próprios dados
sealed class Status {
    data class Sucesso(val dados: List<Item>) : Status()
    data class Erro(val exception: Throwable) : Status()
    data object Carregando : Status()
}
```

## O poder do when exaustivo

A grande sacada das sealed classes é que o compilador sabe todas as subclasses. Quando você usa `when`, ele garante que todos os casos estão cobertos:

```kotlin
fun exibirStatus(status: Status): String = when (status) {
    is Status.Sucesso -> "Carregou ${status.dados.size} itens"
    is Status.Erro -> "Erro: ${status.exception.message}"
    is Status.Carregando -> "Carregando..."
    // Sem `else` necessario! O compilador sabe que cobriu tudo.
}
```

Se amanhã você adicionar um novo subtipo (digamos, `Status.SemConexao`), o compilador vai apontar erro em todos os `when` que não tratam esse novo caso. Isso é poderosíssimo para evitar bugs.

## Caso prático: resultado de chamada de API

Esse é o uso mais clássico de sealed classes no desenvolvimento Android e backend:

```kotlin
sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val message: String, val code: Int? = null) : ApiResult<Nothing>()
    data object Loading : ApiResult<Nothing>()
}

class UsuarioRepository(private val api: ApiService) {
    suspend fun buscarUsuario(id: Int): ApiResult<Usuario> {
        return try {
            val usuario = api.getUsuario(id)
            ApiResult.Success(usuario)
        } catch (e: HttpException) {
            ApiResult.Error("Erro HTTP: ${e.code()}", e.code())
        } catch (e: Exception) {
            ApiResult.Error("Falha na conexão: ${e.message}")
        }
    }
}

// No ViewModel
fun carregarUsuario(id: Int) {
    viewModelScope.launch {
        _uiState.value = ApiResult.Loading

        when (val resultado = repository.buscarUsuario(id)) {
            is ApiResult.Success -> _uiState.value = UiState.Dados(resultado.data)
            is ApiResult.Error -> _uiState.value = UiState.Erro(resultado.message)
            is ApiResult.Loading -> { /* já tratado acima */ }
        }
    }
}
```

## Sealed classes para navegação

Definir rotas de navegação com sealed classes é um padrão muito usado em apps Android:

```kotlin
sealed class Tela(val rota: String) {
    data object Inicio : Tela("inicio")
    data object ListaProdutos : Tela("produtos")
    data class DetalhesProduto(val id: Int) : Tela("produtos/$id")
    data class Perfil(val userId: String) : Tela("perfil/$userId")
    data object Configurações : Tela("configurações")
}

fun navegar(tela: Tela, navController: NavController) {
    when (tela) {
        is Tela.Inicio -> navController.navigate(tela.rota)
        is Tela.ListaProdutos -> navController.navigate(tela.rota)
        is Tela.DetalhesProduto -> navController.navigate(tela.rota)
        is Tela.Perfil -> navController.navigate(tela.rota)
        is Tela.Configurações -> navController.navigate(tela.rota)
    }
}
```

## Sealed interfaces

A partir do Kotlin 1.5, temos também **sealed interfaces**, que são ainda mais flexíveis pois uma classe pode implementar múltiplas sealed interfaces:

```kotlin
sealed interface Evento

sealed interface EventoDeUsuario : Evento {
    data class Login(val email: String) : EventoDeUsuario
    data class Logout(val motivo: String) : EventoDeUsuario
    data class AlterarPerfil(val campo: String, val valor: String) : EventoDeUsuario
}

sealed interface EventoDeSistema : Evento {
    data class ErroDeRede(val codigo: Int) : EventoDeSistema
    data object ConexaoRestabelecida : EventoDeSistema
}

fun tratarEvento(evento: Evento) = when (evento) {
    is EventoDeUsuario.Login -> println("Usuário logou: ${evento.email}")
    is EventoDeUsuario.Logout -> println("Logout: ${evento.motivo}")
    is EventoDeUsuario.AlterarPerfil -> println("Alterou ${evento.campo}")
    is EventoDeSistema.ErroDeRede -> println("Erro de rede: ${evento.codigo}")
    is EventoDeSistema.ConexaoRestabelecida -> println("Conexão OK!")
}
```

## Modelando máquinas de estado

Sealed classes são perfeitas para máquinas de estado:

```kotlin
sealed class EstadoPedido {
    data object Criado : EstadoPedido()
    data class AguardandoPagamento(val valorTotal: Double) : EstadoPedido()
    data class Pago(val transacaoId: String) : EstadoPedido()
    data class EmSeparacao(val previsaoDespacho: LocalDate) : EstadoPedido()
    data class Enviado(val codigoRastreio: String) : EstadoPedido()
    data object Entregue : EstadoPedido()
    data class Cancelado(val motivo: String) : EstadoPedido()
}

fun proximaAcao(estado: EstadoPedido): String = when (estado) {
    is EstadoPedido.Criado -> "Aguardar pagamento"
    is EstadoPedido.AguardandoPagamento -> "Pagar R$ ${estado.valorTotal}"
    is EstadoPedido.Pago -> "Transação ${estado.transacaoId} confirmada. Separar produtos."
    is EstadoPedido.EmSeparacao -> "Despacho previsto: ${estado.previsaoDespacho}"
    is EstadoPedido.Enviado -> "Rastrear: ${estado.codigoRastreio}"
    is EstadoPedido.Entregue -> "Avaliar compra"
    is EstadoPedido.Cancelado -> "Pedido cancelado: ${estado.motivo}"
}
```

## Sealed class vs sealed interface: quando usar qual?

- Use **sealed class** quando as subclasses compartilham estado ou comportamento comum
- Use **sealed interface** quando quer mais flexibilidade com múltipla implementação
- Na dúvida, comece com sealed class — é mais simples de refatorar depois

## Boas práticas

1. **Use sealed classes para estados de UI**: `Loading`, `Success`, `Error` — o padrão clássico
2. **Prefira `data class` e `data object`** para as subclasses — você ganha `toString()`, `equals()` e `copy()` de graça
3. **Evite `else` no `when`**: perca o hábito de usar `else` com sealed classes; assim o compilador te avisa quando um novo caso não foi tratado
4. **Mantenha as subclasses simples**: se uma subclasse está ficando complexa demais, talvez ela precise ser outra sealed class

## Conclusão

Sealed Classes são um daqueles recursos que, depois que você começa a usar, não volta mais atrás. Elas tornam seu código mais seguro, mais expressivo e mais fácil de manter. O compilador vira seu aliado, garantindo que você nunca esqueça de tratar um caso. Um conceito similar existe em <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust com enums e pattern matching</a>, que também garante exaustividade em tempo de compilação.

Se você quer escrever Kotlin idiomático, dominar sealed classes é obrigatório. Mãos à obra!
