Kotlinでは以下コードのように by lazy
を使って簡単に遅延評価処理が書けます。
class Person {
val name by lazy {
//重い処理が走る想定(例:DBからのデータ取得)
"@doyaaaaaken"
}
}
fun main() {
val person = Person() //この時点ではperson.nameはまだ評価されていない(=遅延評価)
println(person.name) //@doyaaaaaken
}
by lazy
はKotlinの言語仕様っぽく見えるのですが、実はそうではなくKotlin標準ライブラリとKotlin言語仕様である委譲プロパティ(Delegated Properties)を使って実現されています。
その仕組みについて説明してみました。
lazyを自前で実装してみる
実際にlazyを自分で簡易に実装してみることで仕組みについて見ていきます。
(簡易実装なので、マルチスレッドに対応していなかったりなどの不備があります。)
自前の関数は myLazy
という名前にし、以下コードのように呼び出し側からは by lazy
ではなく by myLazy
で呼び出すものとしましょう。
class Person {
val name by myLazy {
//重い処理が走る想定(例:DBからのデータ取得)
"@doyaaaaaken"
}
}
myLazy
はグローバルな関数として用意する必要があります。
遅延処理として変数を初期化する処理を initializer
という名前の関数で引数として渡してやりましょう。
MyLazy
というclassが作成されます。
fun <T> myLazy(initializer: () -> T): MyLazy<T> = MyLazy(initializer)
さて、ここでby myLazy
のby
句はKotlinの言語仕様である委譲プロパティという機能です。
詳細な説明は公式ドキュメントを参考にしていただくとして、ここでは簡単な説明だけします。
委譲プロパティは、変数のget, setに関わる共通処理をクラスとして切り出せる(=委譲できる)機能です
今回の場合、「任意の変数をget(=値を取得)する際に遅延評価したい」という要求があり、それをMyLazy
というクラスへと共通化し切り出す(=委譲する)形です。
言語仕様としては、委譲先のクラス内にgetValue
という名前のメソッドを作ることで、by myLazy
をつけた変数(今回の例だとperson.name
)の値が参照される度にそのgetValue
の処理が毎回呼び出されるようになっています。
(getValue
メソッド実装の際には、通常のfun
を使うのではなくoperator fun
として宣言する必要があります。)
class MyLazy<out T>(private val initializer: () -> T) {
private var _value: Any? = UNINITIALIZED_VALUE
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
//CAUTION: not thread safe
if (_value === UNINITIALIZED_VALUE) {
_value = initializer()
}
return _value as T
}
}
private object UNINITIALIZED_VALUE
コード全文
ここまでで説明した諸々のコードの全文は以下のとおりになります。
import kotlin.reflect.KProperty
fun main() {
val person = Person() //この時点ではperson.nameはまだ評価されていない(=遅延評価)
println(person.name) //@doyaaaaaken
}
class Person {
val name by myLazy {
//重い処理が走る想定(例:DBからのデータ取得)
"@doyaaaaaken"
}
}
fun <T> myLazy(initializer: () -> T): MyLazy<T> = MyLazy(initializer)
class MyLazy<out T>(private val initializer: () -> T) {
private var _value: Any? = UNINITIALIZED_VALUE
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
//CAUTION: not thread safe
if (_value === UNINITIALIZED_VALUE) {
_value = initializer()
}
return _value as T
}
}
private object UNINITIALIZED_VALUE