Sealed Class est un type spécial de classe en Kotlin qui restreint la hiérarchie d'héritage à un ensemble fixe de sous-types. Tous les sous-types sont déclarés dans le même fichier et sont connus du compilateur, ce qui permet d'utiliser un bloc when exhaustif sans branche else obligatoire. Selon Kotlin Docs, 2026, les classes scellées sont un mécanisme clé pour représenter des hiérarchies limitées, telles que les états, les types d'erreurs et les événements d'interface utilisateur.
Points essentiels
Sealed Class (classe scellée) est une classe en Kotlin marquée avec le modificateur sealed. Elle définit une hiérarchie de types restreinte : tous les sous-types possibles sont listés dans le même fichier et le compilateur connaît chacun d'eux. Cela distingue sealed class d'une classe ouverte ordinaire dont les sous-types peuvent être déclarés n'importe où.
L'objectif principal de sealed class est la représentation type-safe d'un ensemble fini de variantes. Chaque sous-type peut avoir sa propre structure de données, ce qui rend sealed class plus flexible que enum. À l'exécution, sealed class est une classe abstraite ordinaire ; le compilateur n'impose des restrictions qu'à la compilation.
Sealed class est particulièrement utile dans l'architecture d'applications Android : états d'interface utilisateur, résultats de requêtes réseau, événements de navigation de type Intent, et bien sûr les hiérarchies d'erreurs sont des cas d'utilisation typiques.
À la compilation, sealed class est optimisée en une table de saut pour les expressions when, ce qui la rend plus performante que les chaînes if-else. Combinée avec data class, chaque sous-type peut contenir non seulement un état mais aussi des méthodes, permettant de construire des modèles de domaine auto-documentés sans code passe-partout.
Sealed class est également efficace pour représenter des machines à états dans les applications mobiles. Chaque état est un sous-type séparé avec des paramètres uniques, et les transitions entre états sont contrôlées via des expressions when. Le compilateur garantit que tous les états possibles sont traités, éliminant les erreurs d'exécution lors du changement d'état de l'interface utilisateur ou de la logique métier.
Les développeurs Kotlin débutants confondent souvent sealed class avec enum, car tous deux restreignent l'ensemble des valeurs. Cependant, il existe une différence fondamentale : enum est un ensemble de constantes du même type, tandis que sealed class est une hiérarchie de types différents.
Enum est optimal lorsque toutes les variantes sont des constantes sans structure supplémentaire. Par exemple, les jours de la semaine, les statuts de commande ou les types d'actions sans paramètres. Chaque valeur d'enum est un singleton avec un nom fixe.
Sealed class est nécessaire lorsque chaque variante a ses propres données. Par exemple, une erreur réseau contient un code de réponse, une erreur d'analyse contient des détails, et une erreur d'autorisation contient un message. Chaque sous-type de sealed class est un type séparé avec des champs uniques.
// Enum — toutes les variantes d'un même type
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — chaque variante avec ses propres données
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>()
}
Depuis Kotlin 1.5, il est possible de déclarer sealed interface. Cela étend le concept de scellement aux interfaces : une sealed interface a également un ensemble fixe d'implémentations mais prend en charge l'héritage multiple.
Sealed interface est pratique lorsque les sous-types doivent implémenter plusieurs contrats simultanément. Par exemple, un événement d'interface utilisateur peut être à la fois cliquable et traçable. Avec sealed class, vous devriez choisir une classe de base ; avec sealed interface, le sous-type implémente les deux.
Sealed class est une classe, donc chaque sous-type ne peut avoir qu'un seul parent. Sealed interface résout ce problème mais ne peut pas contenir d'état. Le choix entre eux dépend de la tâche : si une logique partagée avec des champs est nécessaire — utilisez sealed class ; si la flexibilité des contrats est nécessaire — utilisez 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>
}
// Le sous-type implémente les deux interfaces
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
L'une des principales utilisations de sealed class dans le développement mobile est la hiérarchie d'erreurs type-safe. Au lieu de lancer des exceptions de différents types ou d'utiliser une Exception générique, sealed class rassemble toutes les erreurs de domaine possibles en un seul type.
Créez une sealed class DomainError et listez tous les types d'échec comme sous-types. Chaque sous-type contient uniquement les données pertinentes pour ce type d'erreur particulier. Le compilateur garantit que lors du traitement de l'erreur, vous n'oublierez aucune variante.
Considérons une application avec autorisation où différents scénarios d'échec sont possibles : mot de passe incorrect, compte bloqué, problème serveur. Sealed class les combine en un seul type avec un traitement exhaustif.
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 ->
"Tentatives restantes : ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"Accès bloqué jusqu'au ${Date(error.until)}"
is AuthError.NetworkFailure ->
"Vérifiez la connexion : ${error.cause.localizedMessage}"
AuthError.ServerError ->
"Serveur temporairement indisponible"
}
Sealed class est devenue un outil standard dans l'architecture des applications Android. Voyons trois patrons clés où sealed class est indispensable dans le développement mobile.
Il convient également de noter l'utilisation de sealed class dans Clean Architecture. Chaque couche (data, domain, presentation) utilise sealed class pour ses types d'erreur, et les mappers convertissent une sealed class en une autre. Par exemple, DataError de la couche data est mappé vers DomainError pour la logique métier, puis vers UiState pour la couche de présentation. Cela préserve la sécurité des types à tous les niveaux de l'application et garantit qu'aucune erreur ne reste non traitée.
Les tests de sealed class nécessitent une approche spéciale, car chaque sous-type est un type séparé avec son propre état. Il est recommandé d'écrire des tests paramétrés qui parcourent tous les sous-types de sealed class. Cela garantit que les expressions when couvrent toutes les variantes, y compris les nouvelles ajoutées lors de l'extension de la hiérarchie.
Pour les tests d'interface utilisateur, sealed class en tant que UiState permet de vérifier l'affichage de chaque état : Loading montre un spinner, Content montre des données, Error montre un message d'erreur. Puisque sealed class est fini, la couverture de test de tous les états donne une confiance totale dans la correction de la logique d'interface utilisateur.
Malgré la simplicité du concept, les développeurs commettent régulièrement des erreurs lors de la conception de hiérarchies de sealed class. Examinons les principaux problèmes et comment les éviter.
Questions fréquentes
Oui, une sealed class peut contenir des méthodes abstraites, et chaque sous-type est tenu de les implémenter. C'est pratique lorsque toutes les variantes doivent fournir une interface commune mais avec une logique d'exécution différente.
Dans Java 17+, les classes et interfaces scellées avec le modificateur sealed ont été introduites. Android prend actuellement en charge Java 17 partiellement, mais dans les projets Kotlin, sealed class est disponible depuis Kotlin 1.0 sans restrictions.
Oui, une sealed class peut être un sous-type d'une autre. La hiérarchie des sealed classes reste finie : le compilateur connaît tous les sous-types à chaque niveau. Cela permet de construire des classifications d'erreurs détaillées.
Sealed class ne crée pas de surcharge à l'exécution. Le compilateur optimise les expressions when avec les sealed classes en tables de saut (tableswitch), plus rapides que les chaînes if-else. Les performances sont identiques à enum.
Chaque sous-type de sealed class est testé séparément. Puisque sealed class est fini, vous pouvez écrire un test paramétré qui parcourt toutes les variantes. Cela fournit une couverture complète des branches du bloc when.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi