1. 序章

Kotlinでのレイジー初期化は、レイト初期化と簡単に混同される可能性があります。 結局のところ、どちらの場合も、クラスフィールドは初期化されず、後で実際の値が与えられます。 しかし、それはとても簡単ですか?

この記事では、両方のタイプの初期化が正確に何であるか、およびそれらの間に違いがあるかどうかを詳しく見ていきます。

2. 怠惰な初期化

一般に、Kotlinでのレイジー初期化は、デリゲート関数レイジー{}の使用を意味します。 すぐに使用できるデリゲートプロバイダー関数がいくつかあり、lazyはその1つです。 デリゲートプロパティを宣言する通常の方法は、キーワードbyを使用することです。

val specialValue by SomeDelegate(actualValue = "I am Groot")

スレッドセーフなレイジー初期化を作成するのは非常に困難です。 特にドメインが財務またはハードウェア制御である場合、間違いはコストがかかる可能性があります。 lazy デリゲートを使用すると、試行錯誤されたプリミティブを再利用して、考えられるすべての問題を回避できます。

val lazyValue by lazy {
    println("Only now the field is initialized")
    18
}

デフォルトのレイジーデリゲートはkotlin.SynchronizedLazyImplです。 これ同期ロックを取得し、最初の読み取り中にインスタンス化が1つだけになるようにします。 他のすべてのスレッドは、ロックを保持しているスレッドが終了するまで待機します。 以降の読み取りでは、ブロッキングは発生しません。

不変のvalフィールドのみが標準ライブラリのレイジープリミティブを使用できることに注意してください。 生成された怠惰デリゲートにはセッターがないため、変更できません。

3. 後期初期化

一方、レイト初期化は特別な言語のキーワードです。

lateinit var lateValue: ValueType

はvar修飾子の前にのみ表示でき、は非プリミティブ型の変数のみを変更できます。 後から考えると、その理由は明らかです。 プリミティブ型で初期化されていない場合は、その型のデフォルト値が設定されます。 val の場合、後で変更することはできません。

lateinitキーワードは、誰もがアクセスする前にこの参照が確実に値を取得するというコンパイラーへの約束にすぎません。 この約束を破ると、コードはUninitializedPropertyAccessExceptionをスローします。

class LateinitSample {
    lateinit var lateValue: ValueType
}

val sample = LateinitSample()
sample.lateValue // This line throws UninitializedPropertyAccessException

lateinit 変数の適切な使用法は、アクセスする前に変数を初期化することです。

class LateinitSample {
    lateinit var lateValue: ValueType

    fun initBasedOnEnvironment(env: Map<String, String>) {
        lateValue = ValueType(env.toString())
    }
}

val sample = LateinitSample().apply { 
    initBasedOnEnvironment(mapOf("key" to "value"))
}
sample.lateValue // Doesn't throw

ただし、これによりLateinitSampleクラスの使用が煩雑になります。 lateinit キーワードが正当化されるケースはほとんどなく、ほとんどの場合、それを避けるのが最善です

4. 比較

それで、怠惰な初期化と遅い初期化は何らかの形で似ていますか?

レイジー初期化はプロパティDelegate -sの1つですが、レイト初期化では言語キーワードを使用する必要があります。 レイジー初期化はvalにのみ適用され、レイト初期化はvarフィールドにのみ適用されます。 プリミティブ型のレイジーフィールドを持つことができますが、lateinitは参照型にのみ適用されます。

最も重要なことは、フィールドを怠惰なデリゲートとして実装するとき、実際にはある種の値をフィールドに与えていることです。 実際の値の代わりに、必要に応じて計算する関数を配置します。 一方、フィールドを lateinit として宣言する場合は、値を受け取る前にプログラムが変数にアクセスしないことを確認するコンパイラチェックの1つをオフにするだけです。 代わりに、私たちは自分たちでそれをチェックすることを約束します。

したがって、レイジー初期化はレイト初期化とはまったく異なるものであると言っても過言ではありません。

5. 結論

このチュートリアルでは、レイト初期化とレイジー初期化を並べて比較し、それらがほとんど同じものではないことを確認しました。 遅延初期化中に、後でアクセスする場合にフィールドが値を取得する方法を提供します。 後期初期化を使用する場合、フィールドは後になるまで初期化されないままになり、問題が発生する可能性があります。

記事とコードスニペットのすべての例の実装は、GitHubにあります。