kotlinのsealed interfaceについて

採用はこちら

sealed interface は、「実装できる型の集合を コンパイル時に制限 できる interface」です。

通常の interface は、どこからでも自由に実装できます。

しかし sealed interface を使うと、

  • 直接実装(direct implementation)できる型を限定
  • 取り得る型の集合を 設計上“閉じた集合”として定義
  • when 式で 網羅性チェックを効かせる

といったことが可能になります。

sealed interface UiState

この UiState を直接実装できるのは、同一 module かつ同一 package 内で定義されたクラス・オブジェクトに限られます。

(外部の module / package から勝手に実装を増やすことはできません)

目次

sealed interface が解決する設計上の課題

sealed interface は、次のような「設計上の中途半端さ」を解消するために使われます。

よくある悩み

  • enum ではデータを持てず、表現力が足りない
  • interface は自由すぎて、想定外の実装が増えてしまう
  • sealed class を使いたいが、多重継承できないのが困る

sealed interface は、

  • enum より柔軟
  • 通常の interface より安全
  • sealed class より拡張性が高い(多重実装可能)

という中間的かつ実務向きな選択肢です。

基本的な構文と考え方

sealed interface UiState

object Loading : UiState
data class Success(val data: String) : UiState
data class Error(val message: String) : UiState

ここで重要なのは、

  • UiState が取り得る型は Loading / Success / Error のみ
  • この集合は 設計者が管理する範囲でのみ拡張可能
  • 外部ライブラリや別モジュールから勝手に増えない

という点です。

これは「オープンな継承」ではなく、“管理された閉じた多態性” を実現する仕組みと言えます。

when 式との強力な相性(網羅性チェック)

sealed interface 最大の実務的メリットは、when 式での 網羅性チェックです。

fun render(state: UiState) {
    when (state) {
        is Loading -> println("Loading...")
        is Success -> println(state.data)
        is Error -> println(state.message)
    }
}

このコードでは

  • else が不要
  • 新しい UiState の実装を追加すると コンパイルエラー
  • 実行時ではなく 設計・実装段階で漏れに気づける

という安全性が得られます。

補足

特に when式(expression)として使う場合、Kotlin コンパイラは sealed 階層の網羅性をより厳密にチェックします。

sealed class との違い(設計判断の核心)

観点sealed classsealed interface
継承単一継承のみ多重実装可能
コンストラクタ持てる持てない
共通フィールド持てる持てない
主な役割状態+振る舞い型の分類・役割定義
向いている用途基底クラス分類・ポリシー

設計上の使い分け

  • sealed class
    • 共通の状態やロジックを持たせたい
    • 明確な「基底クラス」として振る舞わせたい
  • sealed interface
    • 「取り得る種類」を限定したい
    • 既存クラス階層を壊さず分類したい
    • 多重実装を前提にした設計をしたい

多重実装できる点が sealed interface 最大の強み

sealed interface NetworkResult
sealed interface Cacheable

data class ApiSuccess(val data: String) :
    NetworkResult, Cacheable

sealed class では不可能な、

  • 分類の横断
  • 役割の合成
  • ポリシー的な型設計

が可能になります。

これは DDD(ドメイン駆動設計)やクリーンアーキテクチャにおいて、非常に相性の良い特徴です。

object / data class と組み合わせた柔軟な表現

sealed interface 自体は状態(フィールド)を持てませんが、実装側の自由度は非常に高いです。

sealed interface AuthState

object LoggedOut : AuthState
data class LoggedIn(val userId: String) : AuthState
  • 状態を持たない単一ケース → object
  • データを伴うケース → data class

この構成は enum では実現できない表現力を持ちます。

典型的なユースケース

UI 状態管理(Android / Compose)

sealed interface ScreenState {
    object Loading : ScreenState
    data class Content(val items: List<String>) : ScreenState
    data class Error(val throwable: Throwable) : ScreenState
}

API レスポンス表現

sealed interface ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>
    data class Failure(val error: Throwable) : ApiResult<Nothing>
}

ビジネスルールの分類

sealed interface DiscountPolicy {
    data class Rate(val percent: Int) : DiscountPolicy
    data class Fixed(val amount: Int) : DiscountPolicy
}

設計上のベストプラクティス

「分類」を表すために使う

sealed interface は「共通の振る舞い」より“取り得る型の集合を明示する”用途に向きます。

拡張ポイントとしては使わない

sealed は closed for extension

プラグインや外部拡張を想定する API には不向きです。

public API に使うと理解性が上がる

利用者にとって

  • 取り得る状態が明確
  • when が安全
  • ドキュメント代わりになる

というメリットがあります。

注意点・誤解されやすいポイント

  • sealed interface 自体は 共通フィールドを持てない
  • 実装を増やせるのは 同一 module + 同一 package 内のみ
  • Java との相互運用では、モジュール構成によって見え方が変わる場合がある

まとめ

sealed interface は単なる新機能ではなく、

  • 型安全性の向上
  • 設計意図の明確化
  • 将来変更に強いコード構造

を実現する 設計レベルのツールです。

Kotlin で状態管理・結果型・ドメイン分類を扱うなら、sealed interface は非常に有力な第一候補になります。

以上、kotlinのsealed interfaceについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次