デリゲートプロパティ

kotlinlang.org

Delegated propaties

Delegated propertyにすることで、プロパティに対して、遅延初期化や値の監視などをかんたんに実装することができる。

デリゲートの指定

var delegatedPropety: Type by Delegate()

型の後ろに、byキーワードを使ってデリゲートを指定する。

Standard Delegate

Delegateは自作することができるが、Kotlinは標準で以下のDelegateを用意している。

  • Lazy properties
  • Observable properties
  • 他のプロパティへのデリゲート
  • Storing properties in map

Lazy properties

// lazy()関数は、初期化するための関数リテラルを受け取り、Lazy<T>インスタンスを返す
val lazyValue: String by lazy {
    println("computed")
    "Hello"
}

lazy()デリゲートをつけることで、Lazy<T>インスタンスが返され、 初回のget()が実行された際に、lazy()に渡したラムダが実行される。

ローカル変数をデリゲートプロパティとして宣言することができる。

fun example(computeFoo: () -> Foo) {
    val memoizeFoo by lazy(computeFoo)

Observable properties

Delegates.observable()は2つの引数を取る: 初期値と変更のハンドラー。

import kotlin.properties.Delegate

class User {
    var name: String by Delegates.ovservable("<no name>") {
        prop, old, new -> 
            println("$old -> $new")
    }
}

他のプロパティへのデリゲート

プロパティは、他のプロパティに対してそのgetterとsetterをデリゲートすることができる。

デリゲートプロパティになれるのは、

  • top-level property
  • 同じクラスのメンバ、拡張プロパティ
  • 他のクラスのメンバ、拡張プロパティ
var topLevelInt: Int = 0
class MyClass {
    var delegatedToTopLevel: Int by ::topLevelInt
}
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate(val anotherClassInt: Int) {
    var ddelegedToMember: Int by this::memberInt
    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt

この機能は、deprecatedに対して有効

clas MyClass {
    var newName: Int = 0
    @Deprecated("Use newName insted", ReplaceWith("newName"))
    var oldName: Int by this::newName
}

Storing properties in a map

class User(val map: Map<String, Any?>) {
    val name: String by map // mapプロパティに移譲
    val age: Int     by map
}

nameageはmapから取得される

val json = mapOf("name" to "John Doe", "age" to 25)
val user = User(json)
println(user.name) // John Doe
println(user.age)  // 25

自作Delegate

valプロパティに対しては、getValue()を実装する必要がある。 varプロパティに対しては、setValue()も実装する必要がある。

import kotlin.reflect.KProperty

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

以下のように使える

class Example {
    var p: String by Delegate()
}