Kotlinでのシングルトンの実装について

採用はこちら

Kotlinでは、Javaに比べてシングルトンを非常に安全かつ簡潔に実装できます。

ただし、「書きやすい=何でもシングルトンにしてよい」というわけではありません。

本記事では Kotlin/JVMを前提に、正確な挙動・内部仕様・設計上の注意点まで含めて解説します。

目次

シングルトンとは何か

シングルトン(Singleton)パターンとは、

  • アプリケーション全体で
  • インスタンスが1つだけ存在し
  • どこからでも同じインスタンスにアクセスできる

という設計パターンです。

代表的な用途

  • 設定管理(Config)
  • ロガー(Logger)
  • キャッシュ
  • APIクライアント
  • セッション管理

Kotlinにおける基本形:object

Kotlinでは、object宣言を使うだけでシングルトンを定義できます。

object AppConfig {
    val apiUrl = "https://example.com"
    var debugMode = true

    fun printConfig() {
        println("apiUrl=$apiUrl, debug=$debugMode")
    }
}

使用例

AppConfig.printConfig()
println(AppConfig.apiUrl)

特徴

  • newgetInstance() が不要
  • 常に同一インスタンス
  • 記述量が最小限
  • 可読性が非常に高い

objectの初期化タイミング

基本動作(Kotlin/JVM)

  • object最初に参照されたタイミングで初期化される
  • 実体としては クラス初期化時に1回だけ生成される
object Sample {
    init {
        println("initialized")
    }
}

注意点

  • 「必ず遅延される」と断言はできない
    → フレームワーク、リフレクション、依存関係によって
    意図せず早期に参照される可能性はある

正確な表現

Kotlin/JVMでは、objectはそのオブジェクトを含むクラスが初期化される際に1回だけ初期化され、通常は最初の参照時に発生する。

スレッドセーフ性について

objectの生成自体は安全

  • JVMのクラス初期化はスレッドセーフ
  • そのため objectのインスタンスが複数生成されることはない

Javaで問題になりがちなダブルチェックロッキングを考える必要は基本的にありません。

ただし注意点あり

object 内部の可変状態は別問題です。

object Counter {
    var count = 0
}
  • count++ はスレッドセーフではない
  • インスタンスが1つでも、状態競合は起きる

コンストラクタ引数を持たせたい場合

object主コンストラクタ(引数付き)を持てません

不可

object UserManager(val name: String)

よくある代替パターン

object UserManager {
    lateinit var name: String
        private set

    fun init(name: String) {
        this.name = name
    }
}

注意点

  • 初期化前アクセス → 例外
  • 複数回初期化の危険
  • スレッドセーフではない

設定値や依存注入が必要な場合は、DIやファクトリの方が適切

インターフェース × object

interface Logger {
    fun log(message: String)
}

object ConsoleLogger : Logger {
    override fun log(message: String) {
        println(message)
    }
}

メリット

  • 実装差し替えが容易
  • テストでモック可能
  • DIと相性が良い

「直接 object を使う」のではなく「interface越しに使う」のが設計的に安全

companion objectの正しい理解(誤解が多い)

class User {
    companion object {
        fun create(): User = User()
    }
}

Kotlin的な見え方

User.create()

重要なポイント

  • companion object静的そのものではない
  • 実体は「クラスに紐づくシングルトンオブジェクト」
  • Kotlinからは static のように扱える

Java相互運用

class User {
    companion object {
        @JvmStatic
        fun create(): User = User()
    }
}
  • @JvmStaticを付けると、Javaからも真正な static に見える

objectcompanion object の使い分け

観点objectcompanion object
役割グローバルな唯一の存在クラスに紐づく機能
状態持つことが多い基本は持たない
用途設定・管理・サービスファクトリ・定数

テスト観点での注意

問題点

  • 状態を持つとテスト間で汚染
  • 初期化順序に依存しやすい

対策

  • interface経由で使用
  • 状態を持たせない
  • DIフレームワークを併用

Androidでのシングルトン注意点

NGパターン

  • Activity / Fragment / View を保持
  • 短命オブジェクトの参照を保持

補足

  • Application Contextなら比較的安全な場合もある
  • それでも「保持しない設計」が基本

まとめ

  • Kotlin/JVMでは object がシングルトンの完成形
  • 生成自体は安全だが、内部状態は別途設計が必要
  • companion objectは「static風」だが本質はシングルトン
  • 安易なグローバル状態はテスト性・保守性を下げる

「書ける」より「使っていいか」を常に考えることが重要

以上、Kotlinでのシングルトンの実装についてでした。

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

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