0Pricing
Kotlin Academy · レッスン

カスタムReadWritePropertyデリゲートの作成

ReadWritePropertyを使って、カスタムプロパティデリゲートをゼロから実装します。

「カスタムReadWritePropertyデリゲートの作成」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。

ReadWritePropertyインターフェース

カスタムデリゲートは、getValueおよびsetValue演算子を備えたReadWriteProperty<ThisRef, T>を実装します。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class SimpleDelegate<T>(private var value: T) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}
var x: Int by SimpleDelegate(0)
fun main() { x = 42; println(x) }

val用のReadOnlyProperty

読み取り専用のvalプロパティでは、getValueだけを備えたReadOnlyProperty<ThisRef, T>を実装します。

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class Constant<T>(private val value: T) : ReadOnlyProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}
val pi: Double by Constant(3.14159)
fun main() { println(pi) }

コンテキストにthisRefを使う

thisRefを使うと、デリゲートから外側のオブジェクトにアクセスでき、コンテキストに応じた動作を実現できます。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class LoggingDelegate<T>(private var v: T) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("[${thisRef?.javaClass?.simpleName}] get ${property.name} = $v")
        return v
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        println("[${thisRef?.javaClass?.simpleName}] set ${property.name} = $value")
        v = value
    }
}
class Config { var host: String by LoggingDelegate("localhost") }
fun main() { val c = Config(); c.host = "prod.example.com"; println(c.host) }

数値を範囲内に収めるデリゲート

数値を範囲内に収め、不変条件を透過的に適用するデリゲートです。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class Clamped(private val min: Int, private val max: Int, initial: Int) : ReadWriteProperty<Any?, Int> {
    private var v = initial.coerceIn(min, max)
    override fun getValue(t: Any?, p: KProperty<*>) = v
    override fun setValue(t: Any?, p: KProperty<*>, value: Int) {
        v = value.coerceIn(min, max)
    }
}
var brightness: Int by Clamped(0, 255, 128)
fun main() {
    brightness = 300; println(brightness) // 255
    brightness = -5;  println(brightness) // 0
    brightness = 100; println(brightness) // 100
}

null許容キャッシュデリゲート

値を一度計算してキャッシュし、nullを設定することで無効化できるデリゲートです。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class CachedNullable<T>(private val compute: () -> T) : ReadWriteProperty<Any?, T?> {
    private var cache: T? = null
    override fun getValue(t: Any?, p: KProperty<*>): T? = cache ?: compute().also { cache = it }
    override fun setValue(t: Any?, p: KProperty<*>, value: T?) { cache = value }
}
var expensiveResult: Int? by CachedNullable { (1..1000).sum() }
fun main() {
    println(expensiveResult) // computed
    println(expensiveResult) // cached
    expensiveResult = null
    println(expensiveResult) // recomputed
}

@Synchronizedを使ったスレッドセーフなデリゲート

@Synchronizedまたは内部のロックオブジェクトを使って、デリゲートにスレッドセーフ性を追加します。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class SynchronizedDelegate<T>(private var v: T) : ReadWriteProperty<Any?, T> {
    @Synchronized
    override fun getValue(t: Any?, p: KProperty<*>) = v
    @Synchronized
    override fun setValue(t: Any?, p: KProperty<*>, value: T) { v = value }
}
var counter: Int by SynchronizedDelegate(0)
fun main() { counter = 99; println(counter) }

デリゲートファクトリ関数パターン

標準ライブラリのスタイルに従い、デリゲートオブジェクトを返す拡張関数を通じてデリゲートを公開します。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun <T> logged(initial: T): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
    var v = initial
    override fun getValue(t: Any?, p: KProperty<*>): T { println("get ${p.name}"); return v }
    override fun setValue(t: Any?, p: KProperty<*>, value: T) { println("set ${p.name}=$value"); v = value }
}
var name: String by logged("world")
fun main() { println(name); name = "Kotlin" }

KPropertyメタデータを使うデリゲート

KProperty引数からプロパティ名とアノテーションを取得できます。ログ出力フレームワークやORMで便利です。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class AuditDelegate<T>(private var v: T) : ReadWriteProperty<Any?, T> {
    private val history = mutableListOf<String>()
    override fun getValue(t: Any?, p: KProperty<*>) = v
    override fun setValue(t: Any?, p: KProperty<*>, value: T) {
        history.add("${p.name}: $v -> $value")
        v = value
    }
    fun printHistory() = history.forEach(::println)
}
var status: String by AuditDelegate("idle").also { /* expose audit */ }

provideDelegate演算子

provideDelegateを使うと、デリゲートが委譲の設定に介入し、初期化時にプロパティ名やアノテーションを検証できます。

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class ValidatedDelegate<T>(private val v: T) {
    operator fun provideDelegate(t: Any?, p: KProperty<*>): ReadOnlyProperty<Any?, T> {
        require(p.name.isNotBlank()) { "Property name must not be blank" }
        return ReadOnlyProperty { _, _ -> v }
    }
}
val greeting: String by ValidatedDelegate("Hello")
fun main() { println(greeting) }

アプローチの比較

カスタムデリゲートとobservable/vetoableの比較です。単純なリアクティブパターンには組み込み機能を使用し、複雑な不変条件、キャッシュ、またはコンテキストに応じた動作にはカスタムデリゲートを記述します。

// Built-in: simple change notification
import kotlin.properties.Delegates
var simple: Int by Delegates.observable(0) { _,_,v -> println("changed: $v") }
// Custom: clamping + logging
var complex: Int by run {
    object : kotlin.properties.ReadWriteProperty<Any?,Int> {
        var v = 0
        override fun getValue(t:Any?,p:kotlin.reflect.KProperty<*>)=v
        override fun setValue(t:Any?,p:kotlin.reflect.KProperty<*>,value:Int){
            println("clamping $value"); v=value.coerceIn(0,100)
        }
    }
}
fun main() { simple=5; complex=200; println(complex) }

実践:SharedPreferences デリゲート

Androidでよく使われるパターンです。プロパティをSharedPreferencesに委譲すると、読み書きが透過的にディスクへ永続化されます。

// Pseudocode (no Android runtime here)
class PrefDelegate(private val key: String, private val default: String) {
    // In real Android code:
    // override fun getValue(...) = prefs.getString(key, default) ?: default
    // override fun setValue(...) { prefs.edit().putString(key, value).apply() }
    fun getValue(): String = default // simplified
}
fun main() {
    val delegate = PrefDelegate("user_name", "Guest")
    println(delegate.getValue())
}

クイックチェック

読み書き可能なデリゲートは、どのインターフェースを実装する必要がありますか。

まとめ

カスタムデリゲートはReadWriteProperty(またはReadOnlyProperty)を実装し、ロギング、キャッシュ、検証、値のクランプ、プラットフォームストレージなどを、透過的なプロパティインターフェースの背後にカプセル化できます。

よくある質問

「カスタムReadWritePropertyデリゲートの作成」レッスンは無料ですか?

はい。「カスタムReadWritePropertyデリゲートの作成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。

「カスタムReadWritePropertyデリゲートの作成」で何を学びますか?

ReadWritePropertyを使って、カスタムプロパティデリゲートをゼロから実装します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Kotlin Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「カスタムReadWritePropertyデリゲートの作成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このKotlin Academyレッスンでコードを書いて実行できますか?

はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. by lazy:遅延初期化を深く学ぶ
  2. observableとvetoableデリゲート
  3. カスタムReadWritePropertyデリゲートの作成
  4. Preferences、Map委譲プロパティ、ロギングのデリゲート
← Kotlin Academyに戻る