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 class | sealed 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についてでした。
最後までお読みいただき、ありがとうございました。










