Sealed Class é um tipo especial de classe em Kotlin que restringe a hierarquia de herança a um conjunto fixo de subtipos. Todos os subtipos são declarados no mesmo arquivo e são conhecidos pelo compilador, o que permite usar um bloco when exaustivo sem uma ramificação else obrigatória. De acordo com Kotlin Docs, 2026, as classes seladas são um mecanismo chave para representar hierarquias limitadas, como estados, tipos de erro e eventos de UI.
Principais pontos
Sealed Class (classe selada) é uma classe em Kotlin marcada com o modificador sealed. Ela define uma hierarquia de tipos limitada: todos os subtipos possíveis são listados no mesmo arquivo e o compilador conhece cada um deles. Isso diferencia sealed class de uma classe aberta comum cujos subtipos podem ser declarados em qualquer lugar.
O objetivo principal da sealed class é a representação com segurança de tipos de um conjunto finito de variantes. Cada subtipo pode ter sua própria estrutura de dados, tornando sealed class mais flexível que enum. Em tempo de execução, sealed class é uma classe abstrata comum; o compilador impõe restrições apenas em tempo de compilação.
Sealed class é especialmente útil na arquitetura de aplicativos Android: estados de UI, resultados de requisições de rede, eventos de navegação similares a Intents e, claro, hierarquias de erros são casos de uso típicos.
Em tempo de compilação, sealed class é otimizada em uma tabela de jump para expressões when, tornando-a mais eficiente que cadeias if-else. Combinada com data class, cada subtipo pode conter não apenas estado mas também métodos, permitindo construir modelos de domínio autodocumentados sem código boilerplate.
Sealed class também é eficaz para representar máquinas de estado em aplicativos mobile. Cada estado é um subtipo separado com parâmetros únicos, e as transições entre estados são controladas através de expressões when. O compilador garante que todos os estados possíveis sejam tratados, eliminando erros em tempo de execução ao alterar o estado da UI ou a lógica de negócio.
Desenvolvedores Kotlin iniciantes frequentemente confundem sealed class com enum, já que ambos restringem o conjunto de valores. No entanto, há uma diferença fundamental: enum é um conjunto de constantes do mesmo tipo, enquanto sealed class é uma hierarquia de tipos diferentes.
Enum é ótimo quando todas as variantes são constantes sem estrutura adicional. Por exemplo, dias da semana, status de pedidos ou tipos de ações sem parâmetros. Cada valor de enum é um singleton com um nome fixo.
Sealed class é necessária quando cada variante tem seus próprios dados. Por exemplo, um erro de rede contém um código de resposta, um erro de parsing contém detalhes, e um erro de autorização contém uma mensagem. Cada subtipo de sealed class é um tipo separado com campos únicos.
// Enum — todas as variantes de um único tipo
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — cada variante com seus próprios dados
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
Desde Kotlin 1.5, é possível declarar sealed interface. Isso estende o conceito de selamento para interfaces: uma sealed interface também tem um conjunto fixo de implementações, mas suporta herança múltipla.
Sealed interface é conveniente quando subtipos precisam implementar múltiplos contratos simultaneamente. Por exemplo, um evento de UI pode ser tanto clicável quanto rastreável. Com sealed class, você teria que escolher uma classe base; com sealed interface, o subtipo implementa ambos.
Sealed class é uma classe, então cada subtipo só pode ter um pai. Sealed interface resolve esse problema mas não pode conter estado. A escolha entre eles depende da tarefa: se precisa de lógica compartilhada com campos — use sealed class; se precisa de flexibilidade de contratos — use sealed interface.
sealed interface ScreenEvent {
data class Refresh(val force: Boolean) : ScreenEvent
data class Navigate(val route: String) : ScreenEvent
data class ShowError(val toast: String) : ScreenEvent
}
sealed interface AnalyticsEvent {
val name: String
val params: Map<String, Any>
}
// O subtipo implementa ambas as interfaces
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
Um dos principais usos de sealed class no desenvolvimento mobile é a hierarquia de erros com segurança de tipos. Em vez de lançar exceções de diferentes tipos ou usar uma Exception genérica, sealed class coleta todos os erros possíveis de domínio em um único tipo.
Crie uma sealed class DomainError e liste todos os tipos de falha como subtipos. Cada subtipo contém apenas os dados relevantes para aquele tipo específico de erro. O compilador garante que ao tratar o erro você não esquecerá nenhuma variante.
Considere um aplicativo com autorização onde diferentes cenários de falha são possíveis: senha incorreta, conta bloqueada, problema no servidor. Sealed class os combina em um único tipo com tratamento exaustivo.
sealed class AuthError {
data class InvalidCredentials(
val attempts: Int
) : AuthError()
data class AccountBlocked(
val until: Long
) : AuthError()
data class NetworkFailure(
val cause: Throwable
) : AuthError()
object ServerError : AuthError()
}
fun handleError(error: AuthError): String = when (error) {
is AuthError.InvalidCredentials ->
"Tentativas restantes: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"Acesso bloqueado até ${Date(error.until)}"
is AuthError.NetworkFailure ->
"Verifique a conexão: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"Servidor temporariamente indisponível"
}
Sealed class se tornou uma ferramenta padrão na arquitetura de aplicativos Android. Vamos ver três padrões principais onde sealed class é indispensável no desenvolvimento mobile.
Vale também destacar o uso de sealed class em Clean Architecture. Cada camada (data, domain, presentation) usa sealed class para seus tipos de erro, e os mappers convertem uma sealed class em outra. Por exemplo, DataError da camada de dados é mapeado para DomainError para a lógica de negócio, e então para UiState para a camada de apresentação. Isso preserva a segurança de tipos em todos os níveis do aplicativo e garante que nenhum erro fique sem tratamento.
Testar sealed class requer uma abordagem especial, já que cada subtipo é um tipo separado com seu próprio estado. Recomenda-se escrever testes parametrizados que percorrem todos os subtipos de sealed class. Isso garante que expressões when cubram todas as variantes, incluindo novas adicionadas ao estender a hierarquia.
Para testes de UI, sealed class como UiState permite verificar a exibição de cada estado: Loading mostra um spinner, Content mostra dados, Error mostra uma mensagem de erro. Como sealed class é finito, a cobertura de teste de todos os estados dá total confiança na correção da lógica de UI.
Apesar da simplicidade do conceito, desenvolvedores cometem erros regularmente ao projetar hierarquias de sealed class. Vamos ver os principais problemas e como evitá-los.
Perguntas frequentes
Sim, uma sealed class pode conter métodos abstratos, e cada subtipo é obrigado a implementá-los. Isso é conveniente quando todas as variantes devem fornecer uma interface comum mas com lógica de execução diferente.
Em Java 17+, foram introduzidas classes e interfaces seladas com o modificador sealed. O Android atualmente suporta Java 17 parcialmente, mas em projetos Kotlin, sealed class está disponível desde Kotlin 1.0 sem restrições.
Sim, uma sealed class pode ser subtipo de outra. A hierarquia de sealed classes permanece finita: o compilador conhece todos os subtipos em cada nível. Isso permite construir classificações detalhadas de erros.
Sealed class não cria sobrecarga em tempo de execução. O compilador otimiza expressões when com sealed classes em tabelas de jump (tableswitch), que são mais rápidas que cadeias if-else. O desempenho é idêntico ao de enum.
Cada subtipo de sealed class é testado separadamente. Como sealed class é finito, você pode escrever um teste parametrizado que percorre todas as variantes. Isso fornece cobertura completa dos ramos do bloco when.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também