LoginSignup
3
1

More than 3 years have passed since last update.

Kotlinのlazy(遅延処理)の仕組みを調べて自分で簡易に実装してみた

Last updated at Posted at 2021-03-14

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 myLazyby句は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

参考:公式ドキュメント

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1