はじめに
sealed interface は Kotlin 1.5 以降で登場した新しい概念で、
sealed class の「継承制限」機能を インターフェースにも適用できる ようにしたものです。
これにより、「状態・イベント・結果」などの型階層を、
より柔軟かつ安全に表現できるようになりました。
sealed interface とは?
sealed interface は「同一モジュール内でのみ実装できるインターフェース」です。
sealed class と似ていますが、いくつか重要な違いがあります。
基本構文
sealed interface UiState
data class Success(val data: String) : UiState
object Loading : UiState
object Error : UiState
特徴まとめ
-
同一モジュール内でのみ実装可能(
sealed classは同一ファイル内) - 継承ツリーを限定的かつ柔軟に定義できる
-
when式で網羅性チェックが可能(else不要)
sealed class との違い
| 比較項目 | sealed class | sealed interface |
|---|---|---|
| 継承できる範囲 | 同一ファイル内 | 同一モジュール内 |
| 実装可能数 | 1クラスのみ(単一継承) | 複数のインターフェースを同時実装可 |
| 状態保持 | 可能(プロパティを持てる) | 不可(状態は実装クラス側で保持) |
| 主な用途 | 結果や状態を直接表すモデル | 複数の状態やイベントを表す抽象プロトコル |
なぜ sealed interface が必要なの?
sealed class は単一継承しかできないため、
「複数の概念に属する状態」を表現するのが難しい場合があります。
一方 sealed interface は、
複数の sealed interface を同時に実装できるため、柔軟な状態表現が可能です。
実践例:UI 状態の階層化
例:ロード状態を表す sealed interface
sealed interface UiState
sealed interface LoadingState : UiState
sealed interface ErrorState : UiState
sealed interface SuccessState : UiState
data class Loaded(val data: String) : SuccessState
object Loading : LoadingState
data class Error(val message: String) : ErrorState
各状態が独立していながら、共通して UiState を継承しているため
UI 層で when (state) を使って網羅的に扱えます。
when 式の網羅性チェック
fun render(state: UiState) = when (state) {
is Loaded -> println("データ: ${state.data}")
is Loading -> println("読み込み中…")
is Error -> println("エラー: ${state.message}")
// else 不要!コンパイラが全ケースを認識
}
→ sealed interface でも when の網羅性チェックが効きます。
→ 「型安全な状態管理」 を行うには最適です。
MVI・Reduxパターンでの利用例
sealed interface は MVI (Model–View–Intent) のような
イベント駆動アーキテクチャで特に威力を発揮します。
sealed interface UiEvent {
data object OnClick : UiEvent
data class OnTextChanged(val text: String) : UiEvent
data object OnRetry : UiEvent
}
sealed interface UiState {
object Loading : UiState
data class Success(val data: String) : UiState
data class Error(val message: String) : UiState
}
ViewModel でイベントを受け取り、状態を更新する流れがスッキリ書けます。
fun onEvent(event: UiEvent) {
when (event) {
UiEvent.OnClick -> loadData()
is UiEvent.OnTextChanged -> updateText(event.text)
UiEvent.OnRetry -> retry()
}
}
→ Intent(入力)と State(出力) の両方に sealed interface が使えます。
sealed class と sealed interface の使い分け
| 状況 | 推奨構文 | 理由 |
|---|---|---|
| 状態にデータを持たせたい | sealed class |
プロパティを直接持てる |
| 状態をプロトコル的に定義したい | sealed interface |
複数の型が共通契約を実装できる |
| 状態 + イベントを統一管理したい | sealed interface |
柔軟な構成に向く |
| ファイル単位で閉じたい | sealed class |
継承範囲を完全制限 |
まとめ
- sealed interface は Kotlin 1.5 以降で登場
- 同一モジュール内でのみ実装可能
- 複数の sealed interface を多重実装できる
- when 式の網羅性チェックに対応
- MVI / Redux パターンで特に有効
sealed interface は sealed class の柔軟版です。
状態やイベントを安全かつ拡張性のある形で表現するための必須ツール になりつつあります。
特に Android の MVI や Compose の状態管理においては、
「UI State」「UI Event」「Action」などを sealed interface で定義するのが主流になっています。