Sealed Class — その概要、動作原理、応用

著者: IT Sectr 公開日: 2026-05-26 読了時間: 8 分

Sealed Classは、継承階層を固定のサブタイプセットに制限するKotlinの特別なクラス型です。すべてのサブクラスは同じファイル内で宣言され、コンパイラに認識されるため、必須のelseブランチなしで網羅的なwhenブロックを使用できます。Kotlin Docs, 2026によると、シールドクラスは状態、エラータイプ、UIイベントなどの制限付き階層を表現するための重要なメカニズムです。

重要なポイント

  • Sealed Class — 同じファイル内で宣言された固定のサブクラスセットを持つクラス。
  • Exhaustive when — コンパイラがすべてのサブタイプが処理されたかをチェックし、忘れられたelseブランチを排除。
  • Sealed interface — Kotlin 1.5+は多重継承のためのシールドインターフェースをサポート。
  • エラー階層 — sealed classはKotlinで型安全なエラーハンドリングの標準的な方法。
  • enumとの違い — sealed classの各サブクラスは独自の状態と異なる数のフィールドを持てる。

Sealed Classとは?

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状態やビジネスロジックを変更する際の実行時エラーを排除します。

Sealed ClassとEnum:主な違い

Kotlinの初心者開発者は、両方が値のセットを制限するため、sealed classとenumを混同することがよくあります。しかし、基本的な違いがあります:enumは同じ型の定数のセットであり、sealed classは異なる型の階層です。

enumを選ぶべき場合

Enumは、すべてのバリアントが追加構造のない定数である場合に最適です。例えば、曜日、注文ステータス、パラメータのないアクションタイプなど。各enum値は固定名を持つシングルトンです。

sealed classを選ぶべき場合

Sealed classは、各バリアントが独自のデータを持つ場合に必要です。例えば、ネットワークエラーは応答コードを、パースエラーは詳細を、認証エラーはメッセージを含みます。sealed classの各サブクラスは一意のフィールドを持つ別個の型です。

kotlin
// 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>()
}

Sealed InterfaceとSealed Class

Kotlin 1.5以降、sealed interfaceを宣言できるようになりました。これにより、sealedの概念がインターフェースに拡張され、sealed interfaceも固定の実装セットを持ちますが、多重継承をサポートします。

sealed interfaceを使うべき場合

Sealed interfaceは、サブクラスが複数のコントラクトを同時に実装する必要がある場合に便利です。例えば、UIイベントはクリック可能かつ追跡可能であることができます。sealed classでは1つの基底クラスを選ぶ必要がありますが、sealed interfaceではサブクラスが両方を実装します。

sealed classの制限

Sealed classはクラスであるため、各サブクラスは1つの親しか持てません。Sealed interfaceはこの問題を解決しますが、状態を保持できません。どちらを選ぶかはタスクによります:フィールドを持つ共有ロジックが必要ならsealed class、コントラクトの柔軟性が必要ならsealed interfaceを使用します。

kotlin
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

モバイル開発におけるsealed classの主な用途の1つは、型安全なエラー階層です。異なるタイプの例外をスローしたり、汎用のExceptionを使用する代わりに、sealed classは可能なすべてのドメインエラーを1つの型にまとめます。

エラー階層の構築方法

DomainErrorというsealed classを作成し、すべての障害タイプをサブクラスとして列挙します。各サブクラスには、その特定のエラータイプに関連するデータのみが含まれます。コンパイラはエラーを処理する際にどのバリアントも忘れないことを保証します。

例:認証エラーハンドリング

認証を伴うアプリケーションで、さまざまな障害シナリオが考えられます:間違ったパスワード、アカウント停止、サーバー問題。Sealed classはこれらを網羅的な処理とともに1つの型にまとめます。

kotlin
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 ->
        "サーバーが一時的に利用できません"
}

AndroidでのSealed Class使用パターン

Sealed classはAndroidアプリケーションアーキテクチャの標準的なツールになりました。モバイル開発においてsealed classが不可欠な3つの主要パターンを見てみましょう。

  • UI State — 画面をステートマシンとして表現:Loading、Content、Error。各状態は独自のデータを持ち、sealed classはすべての遷移が処理されることを保証します。
  • Navigation Event — ナビゲーション定数の代わりにsealed classを使用:各画面はルートパラメータを持つ個別のサブクラス。コンパイラが引数の型をチェックします。
  • Action/Intent — Unidirectional Data Flowパターンは、ユーザーが画面で実行できるすべてのアクションを表現するためにsealed classを使用します。

Clean Architectureでのsealed classの使用も注目に値します。各レイヤー(data、domain、presentation)はエラータイプにsealed classを使用し、マッパーがsealed classを別のsealed classに変換します。例えば、データレイヤーのDataErrorはビジネスロジック用にDomainErrorにマッピングされ、さらにプレゼンテーションレイヤー用にUiStateにマッピングされます。これによりアプリケーションのすべてのレベルで型安全性が維持され、どのエラーも未処理のまま残らないことが保証されます。

Sealed Class階層のテスト

テストでは、各サブクラスが独自の状態を持つ別個の型であるため、sealed classには特別なアプローチが必要です。sealed classのすべてのサブクラスを反復処理するパラメータ化テストを作成することをお勧めします。これにより、when式が階層を拡張する際に追加された新しいものを含むすべてのバリアントをカバーすることが保証されます。

UIテストでは、sealed classをUiStateとして使用することで、各状態の表示を確認できます:Loadingはスピナーを表示、Contentはデータを表示、Errorはエラーメッセージを表示。sealed classは有限であるため、すべての状態のテストカバレッジによりUIロジックの正確性に完全な信頼が得られます。

Sealed Class使用時のよくある間違い

コンセプトの単純さにもかかわらず、開発者はsealed class階層を設計する際に定期的に間違いを犯します。主な問題とその回避方法を見てみましょう。

  • 異なるファイル内のサブクラス — サブクラスがファイルの外部にある場合、コンパイラはsealed classの宣言を許可しません。この制限により網羅的なwhenが保証されます。
  • sealedとopenの混在 — sealed classは同時にopenにできません。拡張可能な階層が必要な場合は、通常のabstract classを使用しますが、網羅性は犠牲になります。
  • 過度なネスト — sealed class内のsealed classは、保守が困難な深い階層を作成します。単純なシナリオでは、2レベルで十分です。
  • whenでのelseの忘れ — ライブラリのsealed classに網羅性がない場合、コンパイラは不足しているブランチについて警告しません。elseは意図的にのみ追加してください。

よくある質問

Sealed classは抽象メソッドを持つことができますか?

はい、sealed classは抽象メソッドを含むことができ、各サブクラスはそれらを実装する必要があります。これは、すべてのバリアントが共通のインターフェースを提供する必要があるが、実行ロジックが異なる場合に便利です。

Sealed classはJavaで利用できますか?

Java 17+では、sealed修飾子を持つシールドクラスとインターフェースが導入されました。Androidは現在Java 17を部分的にサポートしていますが、Kotlinプロジェクトではsealed classはKotlin 1.0から制限なく利用できます。

Sealed classは別のsealed classから継承できますか?

はい、あるsealed classは別のsealed classのサブクラスになることができます。sealed class階層は有限のままです:コンパイラは各レベルですべてのサブクラスを認識します。これにより詳細なエラー分類を構築できます。

Sealed classはパフォーマンスに影響しますか?

Sealed classは実行時にオーバーヘッドを発生させません。コンパイラはsealed classを使用するwhen式をジャンプテーブル(tableswitch)に最適化し、if-elseチェーンよりも高速です。パフォーマンスはenumと同じです。

Sealed class階層をテストするには?

Sealed classの各サブクラスは個別にテストされます。sealed classは有限であるため、すべてのバリアントを反復処理するパラメータ化テストを作成できます。これによりwhenブロックのブランチの完全なカバレッジが得られます。

まとめ

  • Sealed class — 1つのファイルで宣言された固定のサブクラスセットを持つクラス。コンパイル時の網羅的なwhen分析を可能にします。
  • Sealed classの各サブクラスは独自のデータ構造を持てます — これがすべてのバリアントが同じ型の定数であるenumとの主な違いです。
  • Sealed interface(Kotlin 1.5+)は多重継承をサポートし、sealed classは単一継承のみサポートします。選択は共有状態の必要性に依存します。
  • Sealed classはKotlinでの型安全なエラー階層のための標準メカニズムです:各障害タイプは関連フィールドを持つ個別のサブクラスです。
  • Androidでの主要パターン:UI StateNavigation EventAction/Intent — 処理の完全性を保証するためにsealed class上に構築されています。
  • 異なるファイル内のサブクラス、過度なネスト、sealedとopenの混在を避けてください — これらは有限階層の契約に違反します。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください