Kotlinの data object は、「シングルトン(object)」と「値として扱いやすい性質(data)」を組み合わせた構文です。
主に 状態・イベント・結果 といった「意味的に一意だが、値として比較・表示したい概念」を表現するために設計されています。
data object の位置づけと導入経緯
data object は比較的新しい機能で、次のような流れで導入されました。
- Kotlin 1.8.20
data objectがプレビュー機能として追加 - Kotlin 1.9.0
安定版(Stable)として正式利用可能に
つまり現在のKotlinでは、言語として確立された正式機能と考えて問題ありません。
なぜ data object が必要になったのか
従来の object の課題
object Loading
object はシングルトンを表現するには非常に便利ですが、次のような問題がありました。
toString()が分かりづらい
→Loading@5f2c3eのような出力になるequals()が参照比較ベース
→ 「値として等しい」という意味をコード上で表しにくい- sealed class の「状態表現」としては少し不自然
data class では解決できなかった理由
data class Loading()
- インスタンスが毎回生成される
- 「Loading という状態が1つしか存在しない」という意図を表現できない
- 設計的にやや無理がある
そこで登場したのが data object
- インスタンスは常に1つ(シングルトン)
- 値として比較・表示しやすい
- sealed class / sealed interface と自然に組み合わせられる
このギャップを埋めるための構文が data object です。
基本構文と挙動
宣言方法
data object Loading
これだけで、次の性質を持ちます。
自動的に提供される機能
data object には以下が自動生成されます。
toString()
→"Loading"equals()
→ 同じdata object同士は等しいhashCode()
→ 安定した値を返す
これらは data class と対称的に扱えるようにするために提供されています。
実際の挙動例
println(Loading) // Loading
println(Loading == Loading) // true
println(Loading === Loading) // true
- JVM上ではシングルトンなので
===もtrue - ログ・デバッグ・テストで非常に読みやすい
data class / object / data object の違い
| 項目 | data class | object | data object |
|---|---|---|---|
| インスタンス数 | 複数 | 1つ | 1つ |
| equals | 値比較 | 参照ベース | 値として等しい |
| toString | 自動生成 | 冗長 | クリーン |
| 主用途 | データ保持 | グローバル存在 | 状態・イベント・結果 |
sealed class / sealed interface との組み合わせ
data object が最も真価を発揮するのが sealed階層です。
典型的な例
sealed class UiState {
data object Loading : UiState()
data object Empty : UiState()
data class Success(val data: List<String>) : UiState()
data class Error(val message: String) : UiState()
}
この設計のメリット
- 状態の網羅性が コンパイル時に保証される
- 「状態が一意である」ことがコードから明確に読み取れる
when式が安全かつ可読的になる
when (state) {
UiState.Loading -> showLoading()
UiState.Empty -> showEmpty()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
enumとの違いと使い分け
data object は enum と比較されがちですが、用途が少し異なります。
enum の特徴
- 列挙値を表現するのに最適
- すべて同じ型・構造
- sealed class と data class を混在させられない
data object の強み
- sealed class / sealed interface の「枝」として使える
data classと共存できる- 将来的な拡張(状態追加・構造変更)に強い
「将来状態が増える」「データを持つ状態と混在させたい」場合は data object が有利です。
実務でよく使われるパターン
UI状態
sealed interface ScreenState {
data object Loading : ScreenState
data object Empty : ScreenState
data class Content(val items: List<String>) : ScreenState
}
処理結果
sealed class Result {
data object Success : Result()
data object Cancelled : Result()
data class Failure(val error: Throwable) : Result()
}
イベント表現
sealed interface UiEvent {
data object OnClick : UiEvent
data object OnRefresh : UiEvent
}
プロパティを持たせる場合の注意点
data object も通常の object と同様に、プロパティを定義すること自体は可能です。
data object Example {
val version = 1
}
ただし、
- 状態を持たない「意味的に一意な存在」を表すのが主目的
- 可変状態を持たせると、
dataの意図から外れやすい
そのため 設計上は「不変・状態なし」を基本にするのが推奨されます。
Javaとの相互運用
- JVM上では通常のシングルトンとして扱われる
- Java側からも問題なく参照可能
toString()/equals()/hashCode()の挙動も期待通り
既存のJavaコードと混在するプロジェクトでも安心して使用できます。
まとめ
data object を使うと良いケース
- 概念的にインスタンスは1つだけ
- 状態・イベント・結果を表現したい
- sealed class / sealed interface と組み合わせたい
- ログやテストで可読性を高めたい
避けた方がよいケース
- 可変な状態を持つ必要がある
- 単なるユーティリティクラス
- 将来的に複数インスタンスが必要
最後に
data object は「この存在は一意で、意味として等価だ」という設計意図を、コードレベルで正確に表現するための機能です。
特に MVVM / MVI / Compose / クリーンアーキテクチャ の文脈では、状態設計の明確さと安全性を大きく向上させてくれます。
以上、Kotlinのdata objectについてでした。
最後までお読みいただき、ありがとうございました。










