[Kotlin]遅延初期化(latinit,lazy)


💡 遅延初期化とは?
一般に、propertienon-null typeと宣言するには、ジェネレータで初期化する必要がある.ただし、コンストラクション関数が初期化できない場合、またはnull typeを回避したい場合は、使用時に初期化するための遅延初期化(Late Initialization,Lazy Initialization)を提供します.
1. Late initialization
  • varの前にlatinitキーワードを使用して宣言します.
  • lateinit var string: String
  • クラスの内部変数またはトップレベル、地域変数として使用できます.
  • non-nullタイプのみが許可され、元のタイプは使用できません.
  • を初期化しないとExceptionが表示されます.
  • lateinit property string has not been initialized
  • latinit varを初期化するかどうか
    初期化の有無を確認するには、propertyの前に::キーワードを使用して.isInitializedを呼び出す.
    class LateInitTest {
        latinit var string: String
        
        @Test
        fun test() {
            // println(string) // Exception
            if (!::string.isInitialized) {
                string = "init"
            }
            println(string)
        }
    }
    2. Lazy initialization
  • valでのみ使用可能で、by lazyを使用して宣言します.
  • val string: String by lazy { "init" }
  • 最初の呼び出し実行はbylazyのramdaに渡され、結果値が保存されます.
  • のデフォルトでは、値は1つのスレッドでのみ計算され、すべてのスレッドに同じ値が表示されます.
  • class LazyTest {
    
        val lazyValue: String by lazy {
            println("computed!")
            "Hello"
        }
    
        @Test
        fun test() {
            println(lazyValue)
            println(lazyValue)
        }
    }
    
    computed!
    Hello
    Hello
    上記の出力結果を表示すると、プログラムが最初にアクセスしたときにram式が呼び出され、ram式の結果値が返されます.
    ふかっせいないぶコード
    private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
        private var initializer: (() -> T)? = initializer
        @Volatile private var _value: Any? = UNINITIALIZED_VALUE
        // final field is required to enable safe publication of constructed instance
        private val lock = lock ?: this
    
        override val value: T
            get() {
                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
    
                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
            }
    
        override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    
        override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    
        private fun writeReplace(): Any = InitializedLazyImpl(value)
    }
    内部コードでは、最初の_valueがUNITIALIZED VALUE(internal object UNINITIALIZED_VALUE)に初期化される.
    get()呼び出しの場合、_valueがUNITIALIZED VALUEでない場合、_valueを返します.
    その後、synchronizedを使用してスレッドのセキュリティを確保し、_valueがUNITIAZED VALUEであるかどうかを再確認し、UNITIAZED VALUEである場合、ram式を呼び出して結果値を保存し、戻ります.
    🙏 の最後の部分lateinitはコンストラクション関数では初期化できませんが、non-nullを使用する場合は特定の時点で初期化し、valueを変更し続ける必要がある場合は使用します.
    特定の時点でlazyを初期化したい場合は、最初のアクセス時にvalueを初期化し、後でvalueを変更する必要がない場合は使用します.
    📜 リファレンス
  • Late-initialized properties and variables
  • Lazy properties
  • Kotlin lazy property-latinit/lazy表示