Sealed Classは、継承階層を固定のサブタイプセットに制限するKotlinの特別なクラス型です。すべてのサブクラスは同じファイル内で宣言され、コンパイラに認識されるため、必須のelseブランチなしで網羅的なwhenブロックを使用できます。Kotlin Docs, 2026によると、シールドクラスは状態、エラータイプ、UIイベントなどの制限付き階層を表現するための重要なメカニズムです。
重要なポイント
Sealed Class(シールドクラス)は、sealed修飾子でマークされたKotlinのクラスです。制限付きの型階層を定義し、可能なすべてのサブクラスが同じファイルに列挙され、コンパイラがそれぞれを認識します。これにより、sealed classはサブクラスをどこでも宣言できる通常のオープンクラスと区別されます。
Sealed classの主な目的は、有限なバリアントセットの型安全な表現です。各サブクラスは独自のデータ構造を持てるため、sealed classはenumよりも柔軟です。実行時には、sealed classは通常の抽象クラスであり、コンパイラはコンパイル時にのみ制約を課します。
Sealed classはAndroidアプリのアーキテクチャで特に有用です:UI状態、ネットワークリクエスト結果、Intentに似たナビゲーションイベント、そしてもちろんエラー階層が典型的な使用例です。
コンパイル時に、sealed classはwhen式のためにジャンプテーブルに最適化され、if-elseチェーンよりも効率的です。data classと組み合わせると、各サブクラスは状態だけでなくメソッドも持つことができ、ボイラープレートコードなしで自己文書化されたドメインモデルを構築できます。
Sealed classはモバイルアプリケーションのステートマシンを表現するのにも効果的です。各状態は一意のパラメータを持つ個別のサブクラスであり、状態間の遷移はwhen式で制御されます。コンパイラはすべての可能な状態が処理されることを保証し、UI状態やビジネスロジックを変更する際の実行時エラーを排除します。
Kotlinの初心者開発者は、両方が値のセットを制限するため、sealed classとenumを混同することがよくあります。しかし、基本的な違いがあります:enumは同じ型の定数のセットであり、sealed classは異なる型の階層です。
Enumは、すべてのバリアントが追加構造のない定数である場合に最適です。例えば、曜日、注文ステータス、パラメータのないアクションタイプなど。各enum値は固定名を持つシングルトンです。
Sealed classは、各バリアントが独自のデータを持つ場合に必要です。例えば、ネットワークエラーは応答コードを、パースエラーは詳細を、認証エラーはメッセージを含みます。sealed classの各サブクラスは一意のフィールドを持つ別個の型です。
// Enum — すべて同じ型のバリアント
enum class Status { LOADING, SUCCESS, ERROR }
// Sealed class — 各バリアントが独自のデータを持つ
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>()
}
Kotlin 1.5以降、sealed interfaceを宣言できるようになりました。これにより、sealedの概念がインターフェースに拡張され、sealed interfaceも固定の実装セットを持ちますが、多重継承をサポートします。
Sealed interfaceは、サブクラスが複数のコントラクトを同時に実装する必要がある場合に便利です。例えば、UIイベントはクリック可能かつ追跡可能であることができます。sealed classでは1つの基底クラスを選ぶ必要がありますが、sealed interfaceではサブクラスが両方を実装します。
Sealed classはクラスであるため、各サブクラスは1つの親しか持てません。Sealed interfaceはこの問題を解決しますが、状態を保持できません。どちらを選ぶかはタスクによります:フィールドを持つ共有ロジックが必要ならsealed class、コントラクトの柔軟性が必要なら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>
}
// サブクラスは両方のインターフェースを実装する
data class LoginClicked(
override val name: String = "login_click",
override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent
モバイル開発におけるsealed classの主な用途の1つは、型安全なエラー階層です。異なるタイプの例外をスローしたり、汎用のExceptionを使用する代わりに、sealed classは可能なすべてのドメインエラーを1つの型にまとめます。
DomainErrorというsealed classを作成し、すべての障害タイプをサブクラスとして列挙します。各サブクラスには、その特定のエラータイプに関連するデータのみが含まれます。コンパイラはエラーを処理する際にどのバリアントも忘れないことを保証します。
認証を伴うアプリケーションで、さまざまな障害シナリオが考えられます:間違ったパスワード、アカウント停止、サーバー問題。Sealed classはこれらを網羅的な処理とともに1つの型にまとめます。
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 ->
"残り試行回数: ${3 - error.attempts}"
is AuthError.AccountBlocked ->
"${Date(error.until)}までアクセスブロック中"
is AuthError.NetworkFailure ->
"接続を確認: ${error.cause.localizedMessage}"
AuthError.ServerError ->
"サーバーが一時的に利用できません"
}
Sealed classはAndroidアプリケーションアーキテクチャの標準的なツールになりました。モバイル開発においてsealed classが不可欠な3つの主要パターンを見てみましょう。
Clean Architectureでのsealed classの使用も注目に値します。各レイヤー(data、domain、presentation)はエラータイプにsealed classを使用し、マッパーがsealed classを別のsealed classに変換します。例えば、データレイヤーのDataErrorはビジネスロジック用にDomainErrorにマッピングされ、さらにプレゼンテーションレイヤー用にUiStateにマッピングされます。これによりアプリケーションのすべてのレベルで型安全性が維持され、どのエラーも未処理のまま残らないことが保証されます。
テストでは、各サブクラスが独自の状態を持つ別個の型であるため、sealed classには特別なアプローチが必要です。sealed classのすべてのサブクラスを反復処理するパラメータ化テストを作成することをお勧めします。これにより、when式が階層を拡張する際に追加された新しいものを含むすべてのバリアントをカバーすることが保証されます。
UIテストでは、sealed classをUiStateとして使用することで、各状態の表示を確認できます:Loadingはスピナーを表示、Contentはデータを表示、Errorはエラーメッセージを表示。sealed classは有限であるため、すべての状態のテストカバレッジによりUIロジックの正確性に完全な信頼が得られます。
コンセプトの単純さにもかかわらず、開発者はsealed class階層を設計する際に定期的に間違いを犯します。主な問題とその回避方法を見てみましょう。
よくある質問
はい、sealed classは抽象メソッドを含むことができ、各サブクラスはそれらを実装する必要があります。これは、すべてのバリアントが共通のインターフェースを提供する必要があるが、実行ロジックが異なる場合に便利です。
Java 17+では、sealed修飾子を持つシールドクラスとインターフェースが導入されました。Androidは現在Java 17を部分的にサポートしていますが、Kotlinプロジェクトではsealed classはKotlin 1.0から制限なく利用できます。
はい、あるsealed classは別のsealed classのサブクラスになることができます。sealed class階層は有限のままです:コンパイラは各レベルですべてのサブクラスを認識します。これにより詳細なエラー分類を構築できます。
Sealed classは実行時にオーバーヘッドを発生させません。コンパイラはsealed classを使用するwhen式をジャンプテーブル(tableswitch)に最適化し、if-elseチェーンよりも高速です。パフォーマンスはenumと同じです。
Sealed classの各サブクラスは個別にテストされます。sealed classは有限であるため、すべてのバリアントを反復処理するパラメータ化テストを作成できます。これによりwhenブロックのブランチの完全なカバレッジが得られます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。