Kotlinの Delegate(デリゲート) は、単なる糖衣構文ではなく、責務分離・再利用性・保守性 を高いレベルで実現するための言語機能です。
Delegateを正しく理解すると、
- 状態管理コードの肥大化を防げる
- ログ・監視・永続化などの横断的関心事を分離できる
- 継承に頼らない柔軟な設計が可能になる
といった実務的メリットが得られます。
本記事では、仕様として正確な説明をベースに実務視点でKotlinのDelegateを体系的に解説します。
KotlinにおけるDelegateとは何か
Delegateとは 「処理や責務を別のオブジェクトに委譲する仕組み」 です。
Kotlinでは、Delegateは主に次の2つの文脈で使われます。
- プロパティデリゲート
- クラス委譲(インターフェース委譲)
どちらも「by」を使いますが、目的と役割は明確に異なります。
プロパティデリゲートの基本構造
基本構文
var name: String by delegate
このとき、name へのアクセスは直接フィールドを触るのではなく、delegate オブジェクト経由 で処理されます。
内部的に行われること
Kotlinコンパイラは、次のような呼び出しを生成します。
delegate.getValue(thisRef, property)
delegate.setValue(thisRef, property, value)
引数の意味
thisRef- プロパティの所有者インスタンス
- トップレベルプロパティなどでは
nullになる場合もある
property- 対象プロパティのメタ情報(
KProperty<*>)
- 対象プロパティのメタ情報(
この仕組みにより、
- 値の保持方法
- 取得・代入時の副作用
- 検証・制約
を プロパティ自身から完全に分離 できます。
標準ライブラリの代表的なプロパティデリゲート
Kotlin標準ライブラリには、実務で頻出するデリゲートが用意されています。
lazy:遅延初期化
val data: String by lazy {
println("初期化処理")
"Hello"
}
特徴
- 初回アクセス時にのみ初期化される
val専用(再代入不可)- デフォルトではスレッドセーフ
重要な補足(正確な仕様)
lazy はスレッドセーフかどうかを モード指定 できます。
lazy(LazyThreadSafetyMode.SYNCHRONIZED) // デフォルト
lazy(LazyThreadSafetyMode.PUBLICATION)
lazy(LazyThreadSafetyMode.NONE) // 非スレッドセーフだが高速
つまり「lazyは常にスレッドセーフ」という理解は誤りで、デフォルトがスレッドセーフ というのが正確です。
実務での利用例
- Repository や API クライアント
- 重い初期化処理
- DI コンテナと組み合わせた遅延生成
observable:変更監視
var count: Int by Delegates.observable(0) { property, old, new ->
println("$old -> $new")
}
動作タイミング
- 値が 代入された後 にコールバックが呼ばれる
- 新しい値はすでに反映済み
注意点(重要)
- 「値が変わったとき」ではなく、「代入が行われたとき」に呼ばれる
- 同じ値を再代入しても呼ばれる可能性がある
UI更新、ログ出力、状態監視などに向いています。
vetoable:代入の拒否
var age: Int by Delegates.vetoable(0) { _, old, new ->
new >= 0
}
特徴
- 代入前に呼ばれる
falseを返すと代入されない- 例外は投げられず、値は旧値のまま保持される
observable が「通知」用途なのに対し、vetoable は 制約・バリデーション に向いています。
notNull:非null保証(遅延初期化)
var userId: String by Delegates.notNull()
仕様上のポイント
- 初期値を持たない
varに使える - 未初期化状態でアクセスすると例外
lateinitが使えない型(Intなど)にも使える
lateinit と異なり、ジェネリックとして実装されている点が特徴です。
カスタムプロパティデリゲート
最小構成
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Hello"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("${property.name} = $value")
}
}
var text: String by MyDelegate()
実務ではジェネリックが基本
class LoggingDelegate<T>(private var value: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("get ${property.name}")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("set ${property.name}: $value -> $newValue")
value = newValue
}
}
よくある用途
- ログ付きプロパティ
- 永続化(SharedPreferences / DataStore)
- バリデーション付き代入
- キャッシュ制御
プロパティの「横断的関心事」を切り出すのに非常に有効です。
クラス委譲(インターフェース委譲)
Kotlinには クラス(正確にはインターフェース)委譲 という仕組みもあります。
class MyList(
private val list: List<String>
) : List<String> by list
重要な前提
- 委譲できるのは インターフェースの実装
- 任意のクラス継承を
byで委譲できるわけではない
一部メソッドだけ拡張する例
class MyList(
private val list: MutableList<String>
) : MutableList<String> by list {
override fun add(element: String): Boolean {
println("add: $element")
return list.add(element)
}
}
実務的メリット
- Decoratorパターンを簡潔に実装できる
- 継承より疎結合
- テストや差し替えが容易
Delegateは設計のための機能
Delegateは単なる省略構文ではなく、設計を支援する仕組み です。
Delegateが特に向いているケース
- ログ・監視・永続化などの横断的関心事
- 状態管理の分離
- 継承を避けたい場合の振る舞い拡張
継承との比較
| 観点 | Delegate | 継承 |
|---|---|---|
| 柔軟性 | 高い | 低い |
| 再利用性 | 高い | 限定的 |
| 結合度 | 低 | 高 |
| テスト | 容易 | 難しい |
まとめ
KotlinのDelegateは、
- プロパティの振る舞いを外部化できる
- クラスの実装を安全に委譲できる
- 設計の見通しを大幅に改善できる
という点で、Kotlinらしさを象徴する機能です。
以上、KotlinのDelegateについてでした。
最後までお読みいただき、ありがとうございました。










