Kotlinのdata objectについて

採用はこちら

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 classobjectdata 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についてでした。

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

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