Sealed Class es un tipo especial de clase en Kotlin que limita la jerarquía de herencia a un conjunto fijo de subtipos. Todos los subtipos se declaran en el mismo archivo y el compilador los conoce, lo que permite usar un bloque when exhaustivo sin una rama else obligatoria. Según Kotlin Docs, 2026, las clases selladas son un mecanismo clave para representar jerarquías limitadas, como estados, tipos de error y eventos de UI.
Puntos clave
Sealed Class (clase sellada) es una clase en Kotlin marcada con el modificador sealed. Define una jerarquía de tipos limitada: todos los subtipos posibles se enumeran en el mismo archivo y el compilador conoce cada uno de ellos. Esto diferencia a sealed class de una clase abierta normal cuyos subtipos pueden declararse en cualquier lugar.
El propósito principal de sealed class es la representación con seguridad de tipos de un conjunto finito de variantes. Cada subtipo puede tener su propia estructura de datos, lo que hace que sealed class sea más flexible que enum. En tiempo de ejecución, sealed class es una clase abstracta normal; el compilador impone restricciones solo en tiempo de compilación.
Sealed class es especialmente útil en la arquitectura de aplicaciones Android: estados de UI, resultados de solicitudes de red, eventos de navegación similares a Intents y, por supuesto, jerarquías de errores son casos de uso típicos.
En tiempo de compilación, sealed class se optimiza en una tabla de saltos para expresiones when, lo que la hace más eficiente que las cadenas if-else. Combinada con data class, cada subtipo puede contener no solo estado sino también métodos, lo que permite crear modelos de dominio autodocumentados sin código boilerplate.
Sealed class también es eficaz para representar máquinas de estado en aplicaciones móviles. Cada estado es un subtipo separado con parámetros únicos, y las transiciones entre estados se controlan mediante expresiones when. El compilador garantiza que todos los estados posibles sean manejados, eliminando errores en tiempo de ejecución al cambiar el estado de la UI o la lógica de negocio.
Los desarrolladores principiantes de Kotlin a menudo confunden sealed class con enum, ya que ambos restringen el conjunto de valores. Sin embargo, existe una diferencia fundamental: enum es un conjunto de constantes del mismo tipo, mientras que sealed class es una jerarquía de tipos diferentes.
Enum es óptimo cuando todas las variantes son constantes sin estructura adicional. Por ejemplo, días de la semana, estados de pedido o tipos de acciones sin parámetros. Cada valor de enum es un singleton con un nombre fijo.
Sealed class es necesaria cuando cada variante tiene sus propios datos. Por ejemplo, un error de red contiene un código de respuesta, un error de análisis contiene detalles, y un error de autorización contiene un mensaje. Cada subtipo de sealed class es un tipo separado con campos únicos.
// Enum — todas las variantes de un mismo tipo
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — cada variante con sus propios datos
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, es posible declarar sealed interface. Esto extiende el concepto de sellado a las interfaces: una sealed interface también tiene un conjunto fijo de implementaciones, pero admite herencia múltiple.
Sealed interface es conveniente cuando los subtipos necesitan implementar varios contratos simultáneamente. Por ejemplo, un evento de UI puede ser a la vez cliqueable y rastreable. Con sealed class, tendrías que elegir una clase base; con sealed interface, el subtipo implementa ambos.
Sealed class es una clase, por lo que cada subtipo solo puede tener un padre. Sealed interface resuelve este problema pero no puede contener estado. La elección entre ellos depende de la tarea: si se necesita lógica compartida con campos — usa sealed class; si se necesita flexibilidad de contratos — usa 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>
}
// El subtipo implementa ambas interfaces
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
Uno de los usos principales de sealed class en el desarrollo móvil son las jerarquías de errores con seguridad de tipos. En lugar de lanzar excepciones de diferentes tipos o usar una Exception general, sealed class reúne todos los errores posibles del dominio en un solo tipo.
Crea una sealed class DomainError y enumera todos los tipos de fallo como subtipos. Cada subtipo contiene solo los datos relevantes para ese tipo de error en particular. El compilador garantiza que al manejar el error no olvidarás ninguna variante.
Considera una aplicación con autorización donde son posibles diferentes escenarios de fallo: contraseña incorrecta, cuenta bloqueada, problema con el servidor. Sealed class los combina en un solo tipo con manejo exhaustivo.
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 ->
"Intentos restantes: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"Acceso bloqueado hasta ${Date(error.until)}"
is AuthError.NetworkFailure ->
"Verifique la conexión: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"Servidor temporalmente no disponible"
}
Sealed class se ha convertido en una herramienta estándar en la arquitectura de aplicaciones Android. Veamos tres patrones clave donde sealed class es indispensable en el desarrollo móvil.
También cabe destacar el uso de sealed class en Clean Architecture. Cada capa (data, domain, presentation) usa sealed class para sus tipos de error, y los mappers convierten una sealed class en otra. Por ejemplo, DataError de la capa de datos se mapea a DomainError para la lógica de negocio, y luego a UiState para la capa de presentación. Esto preserva la seguridad de tipos en todos los niveles de la aplicación y garantiza que ningún error quede sin manejar.
Las pruebas de sealed class requieren un enfoque especial, ya que cada subtipo es un tipo separado con su propio estado. Se recomienda escribir pruebas parametrizadas que iteran sobre todos los subtipos de sealed class. Esto garantiza que las expresiones when cubran todas las variantes, incluidas las nuevas añadidas al extender la jerarquía.
Para las pruebas de UI, sealed class como UiState permite verificar la visualización de cada estado: Loading muestra un spinner, Content muestra datos, Error muestra un mensaje de error. Dado que sealed class es finito, la cobertura de pruebas de todos los estados brinda total confianza en la corrección de la lógica de UI.
A pesar de la simplicidad del concepto, los desarrolladores cometen errores regularmente al diseñar jerarquías de sealed class. Veamos los problemas principales y cómo evitarlos.
Preguntas frecuentes
Sí, una sealed class puede contener métodos abstractos, y cada subtipo está obligado a implementarlos. Esto es útil cuando todas las variantes deben proporcionar una interfaz común pero con diferente lógica de ejecución.
En Java 17+ se introdujeron las clases e interfaces selladas con el modificador sealed. Android actualmente admite Java 17 parcialmente, pero en proyectos Kotlin, sealed class está disponible desde Kotlin 1.0 sin restricciones.
Sí, una sealed class puede ser subtipo de otra. La jerarquía de sealed classes sigue siendo finita: el compilador conoce todos los subtipos en cada nivel. Esto permite construir clasificaciones detalladas de errores.
Sealed class no genera sobrecarga en tiempo de ejecución. El compilador optimiza las expresiones when con sealed classes en tablas de salto (tableswitch), que son más rápidas que las cadenas if-else. El rendimiento es idéntico al de enum.
Cada subtipo de sealed class se prueba por separado. Dado que sealed class es finito, se puede escribir una prueba parametrizada que itere sobre todas las variantes. Esto proporciona una cobertura completa de las ramas del bloque when.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también