Sealed Class è un tipo speciale di classe in Kotlin che limita la gerarchia di ereditarietà a un insieme fisso di sottotipi. Tutti i sottotipi sono dichiarati nello stesso file e sono noti al compilatore, consentendo di utilizzare un blocco when esaustivo senza un ramo else obbligatorio. Secondo Kotlin Docs, 2026, le classi sigillate sono un meccanismo chiave per rappresentare gerarchie limitate, come stati, tipi di errore ed eventi dell'interfaccia utente.
Punti chiave
Sealed Class (classe sigillata) è una classe in Kotlin contrassegnata con il modificatore sealed. Definisce una gerarchia di tipi limitata: tutti i possibili sottotipi sono elencati nello stesso file e il compilatore conosce ciascuno di essi. Questo distingue sealed class da una normale classe aperta i cui sottotipi possono essere dichiarati ovunque.
Lo scopo principale di sealed class è la rappresentazione type-safe di un insieme finito di varianti. Ogni sottotipo può avere una propria struttura dati, rendendo sealed class più flessibile di enum. In fase di esecuzione, sealed class è una normale classe astratta; il compilatore impone restrizioni solo in fase di compilazione.
Sealed class è particolarmente utile nell'architettura delle applicazioni Android: stati dell'interfaccia utente, risultati di richieste di rete, eventi di navigazione simili a Intent e, naturalmente, gerarchie di errori sono casi d'uso tipici.
In fase di compilazione, sealed class viene ottimizzata in una tabella di salto per le espressioni when, rendendola più efficiente delle catene if-else. Combinata con data class, ogni sottotipo può contenere non solo stato ma anche metodi, consentendo di costruire modelli di dominio auto-documentanti senza codice boilerplate.
Sealed class è anche efficace per rappresentare macchine a stati nelle applicazioni mobili. Ogni stato è un sottotipo separato con parametri unici e le transizioni tra stati sono controllate tramite espressioni when. Il compilatore garantisce che tutti gli stati possibili siano gestiti, eliminando errori in fase di esecuzione quando si cambia lo stato dell'interfaccia utente o la logica di business.
Gli sviluppatori Kotlin principianti spesso confondono sealed class con enum, poiché entrambi limitano l'insieme dei valori. Tuttavia, c'è una differenza fondamentale: enum è un insieme di costanti dello stesso tipo, mentre sealed class è una gerarchia di tipi diversi.
Enum è ottimale quando tutte le varianti sono costanti senza struttura aggiuntiva. Ad esempio, giorni della settimana, stati degli ordini o tipi di azioni senza parametri. Ogni valore enum è un singleton con un nome fisso.
Sealed class è necessaria quando ogni variante ha i propri dati. Ad esempio, un errore di rete contiene un codice di risposta, un errore di parsing contiene dettagli e un errore di autorizzazione contiene un messaggio. Ogni sottotipo di sealed class è un tipo separato con campi unici.
// Enum — tutte le varianti dello stesso tipo
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — ogni variante con i propri dati
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>()
}
Da Kotlin 1.5, è possibile dichiarare sealed interface. Questo estende il concetto di sigillatura alle interfacce: una sealed interface ha anche un insieme fisso di implementazioni ma supporta l'ereditarietà multipla.
Sealed interface è comoda quando i sottotipi devono implementare più contratti contemporaneamente. Ad esempio, un evento dell'interfaccia utente può essere sia cliccabile che tracciabile. Con sealed class, dovresti scegliere una classe base; con sealed interface, il sottotipo implementa entrambi.
Sealed class è una classe, quindi ogni sottotipo può avere un solo genitore. Sealed interface risolve questo problema ma non può contenere stato. La scelta tra loro dipende dal compito: se serve logica condivisa con campi — usa sealed class; se serve flessibilità di contratti — 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>
}
// Il sottotipo implementa entrambe le interfacce
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
Uno degli usi principali di sealed class nello sviluppo mobile è la gerarchia di errori type-safe. Invece di lanciare eccezioni di diversi tipi o usare un'Exception generica, sealed class raccoglie tutti i possibili errori di dominio in un unico tipo.
Crea una sealed class DomainError ed elenca tutti i tipi di fallimento come sottotipi. Ogni sottotipo contiene solo i dati rilevanti per quel particolare tipo di errore. Il compilatore garantisce che durante la gestione dell'errore non dimenticherai alcuna variante.
Considera un'applicazione con autorizzazione dove sono possibili diversi scenari di fallimento: password errata, account bloccato, problema del server. Sealed class li combina in un unico tipo con gestione esaustiva.
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 ->
"Tentativi rimanenti: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"Accesso bloccato fino al ${Date(error.until)}"
is AuthError.NetworkFailure ->
"Controlla la connessione: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"Server temporaneamente non disponibile"
}
Sealed class è diventata uno strumento standard nell'architettura delle applicazioni Android. Vediamo tre pattern principali in cui sealed class è indispensabile nello sviluppo mobile.
Vale anche la pena notare l'uso di sealed class in Clean Architecture. Ogni strato (data, domain, presentation) usa sealed class per i propri tipi di errore, e i mapper convertono una sealed class in un'altra. Ad esempio, DataError dallo strato dati viene mappato in DomainError per la logica di business, e poi in UiState per lo strato di presentazione. Questo preserva la sicurezza dei tipi a tutti i livelli dell'applicazione e garantisce che nessun errore rimanga non gestito.
Il testing di sealed class richiede un approccio speciale, poiché ogni sottotipo è un tipo separato con il proprio stato. Si consiglia di scrivere test parametrizzati che iterano su tutti i sottotipi di sealed class. Questo garantisce che le espressioni when coprano tutte le varianti, incluse quelle nuove aggiunte quando si estende la gerarchia.
Per i test dell'interfaccia utente, sealed class come UiState consente di verificare la visualizzazione di ogni stato: Loading mostra uno spinner, Content mostra dati, Error mostra un messaggio di errore. Poiché sealed class è finito, la copertura dei test di tutti gli stati dà completa fiducia nella correttezza della logica dell'interfaccia utente.
Nonostante la semplicità del concetto, gli sviluppatori commettono regolarmente errori nella progettazione di gerarchie di sealed class. Vediamo i problemi principali e come evitarli.
Domande frequenti
Sì, una sealed class può contenere metodi astratti e ogni sottotipo è obbligato a implementarli. Questo è comodo quando tutte le varianti devono fornire un'interfaccia comune ma con logica di esecuzione diversa.
In Java 17+, sono state introdotte classi e interfacce sigillate con il modificatore sealed. Android attualmente supporta Java 17 parzialmente, ma nei progetti Kotlin, sealed class è disponibile da Kotlin 1.0 senza restrizioni.
Sì, una sealed class può essere un sottotipo di un'altra. La gerarchia delle sealed class rimane finita: il compilatore conosce tutti i sottotipi a ogni livello. Questo consente di costruire classificazioni dettagliate degli errori.
Sealed class non crea overhead in fase di esecuzione. Il compilatore ottimizza le espressioni when con sealed class in tabelle di salto (tableswitch), più veloci delle catene if-else. Le prestazioni sono identiche a enum.
Ogni sottotipo di sealed class viene testato separatamente. Poiché sealed class è finito, puoi scrivere un test parametrizzato che iteri su tutte le varianti. Questo fornisce una copertura completa dei rami del blocco when.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche