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)
特徴
newやgetInstance()が不要- 常に同一インスタンス
- 記述量が最小限
- 可読性が非常に高い
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に見える
object と companion object の使い分け
| 観点 | object | companion object |
|---|---|---|
| 役割 | グローバルな唯一の存在 | クラスに紐づく機能 |
| 状態 | 持つことが多い | 基本は持たない |
| 用途 | 設定・管理・サービス | ファクトリ・定数 |
テスト観点での注意
問題点
- 状態を持つとテスト間で汚染
- 初期化順序に依存しやすい
対策
- interface経由で使用
- 状態を持たせない
- DIフレームワークを併用
Androidでのシングルトン注意点
NGパターン
- Activity / Fragment / View を保持
- 短命オブジェクトの参照を保持
補足
- Application Contextなら比較的安全な場合もある
- それでも「保持しない設計」が基本
まとめ
- Kotlin/JVMでは
objectがシングルトンの完成形 - 生成自体は安全だが、内部状態は別途設計が必要
companion objectは「static風」だが本質はシングルトン- 安易なグローバル状態はテスト性・保守性を下げる
「書ける」より「使っていいか」を常に考えることが重要
以上、Kotlinでのシングルトンの実装についてでした。
最後までお読みいただき、ありがとうございました。









