Kotlin-「lazy」と「lateinit」を使用したプロパティの初期化


280

Kotlinでは、コンストラクター内またはクラス本体の上部でクラスプロパティを初期化しない場合、基本的に次の2つのオプションがあります(言語参照から)。

  1. 遅延初期化

lazy()はラムダを取り、レイジープロパティを実装するためのデリゲートとして機能するLazyのインスタンスを返す関数です。最初のget()の呼び出しは、lazy()に渡されたラムダを実行し、結果を記憶します。 get()は単に記憶された結果を返します。

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

最初の呼び出しとsubquential呼び出しがだから、それはどこにmyLazyString戻ります「こんにちは」

  1. 遅い初期化

通常、null以外の型として宣言されたプロパティは、コンストラクターで初期化する必要があります。ただし、これは不便なことがよくあります。たとえば、プロパティは、依存関係の注入、または単体テストのセットアップメソッドで初期化できます。この場合、コンストラクターでnull以外の初期化子を指定することはできませんが、クラスの本体内のプロパティを参照するときにnullチェックを回避する必要があります。

この場合に対処するには、lateinit修飾子を使用してプロパティをマークします。

public class MyTest {
   
   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

修飾子は、クラスの本体内で宣言された(プライマリコンストラクターではなく)varプロパティでのみ使用でき、プロパティにカスタムゲッターまたはセッターがない場合にのみ使用できます。プロパティのタイプはnull以外である必要があり、プリミティブタイプであってはなりません。

では、これらの2つのオプションはどちらも同じ問題を解決できるので、これらのオプションを正しく選択するにはどうすればよいでしょうか。

回答:


334

lateinit varby lazy { ... }委任プロパティの主な違いは次のとおりです。

  • lazy { ... }デリゲートはvalプロパティにのみ使用できますが、フィールドにコンパイルできないためsにlateinitのみ適用でき、不変性は保証されません。varfinal

  • lateinit var値を格納するバッキングフィールドがby lazy { ... }あり、値が計算されると格納されるデリゲートオブジェクトを作成し、クラスオブジェクトにデリゲートインスタンスへの参照を格納し、デリゲートインスタンスで動作するプロパティのゲッターを生成します。したがって、クラスにバッキングフィールドが必要な場合は、lateinit;を使用します。

  • に加えてvallateinitnull不可のプロパティおよびJavaプリミティブ型には使用できません(これはnull、初期化されていない値に使用されるためです)。

  • lateinit varフレームワークコード内など、オブジェクトが見える場所から初期化できます。また、単一クラスの異なるオブジェクトに対して複数の初期化シナリオが可能です。by lazy { ... }は、次に、プロパティの唯一の初期化子を定義します。これは、サブクラスでプロパティをオーバーライドすることによってのみ変更できます。プロパティを外部から初期化する必要がある場合は、おそらく事前に不明な方法で、を使用しますlateinit

  • 初期化by lazy { ... }はデフォルトでスレッドセーフであり、イニシャライザが最大で1回呼び出されることを保証します(ただし、これは別のlazyオーバーロードを使用して変更できます)。の場合lateinit var、マルチスレッド環境でプロパティを正しく初期化するかどうかは、ユーザーのコード次第です。

  • Lazyインスタンスは、保存されて周り渡されても、複数のプロパティのために使用することができます。逆に、lateinit varsは追加のランタイム状態を格納しません(null初期化されていない値のフィールドにのみ)。

  • のインスタンスへの参照を保持している場合はLazyisInitialized()それがすでに初期化されているかどうかを確認できます(委任されたプロパティからのリフレクションでそのようなインスタンスを取得できます)。lateinitプロパティが初期化されているかどうかを確認するには、Kotlin 1.2以降使用property::isInitializedできます

  • 渡されたラムダはby lazy { ... }、それがそのクロージャに使用されているコンテキストからの参照をキャプチャする場合があります。その後、参照を格納し、プロパティが初期化された後にのみそれらを解放します。これにより、Androidアクティビティなどのオブジェクト階層があまりにも長い間解放されない可能性があります(または、プロパティがアクセス可能なままでアクセスされない場合)、イニシャライザラムダ内で何を使用するかに注意する必要があります。

また、質問で言及されていない別の方法がありDelegates.notNull()ます。これは、Javaプリミティブ型のプロパティを含む、null以外のプロパティの遅延初期化に適しています。


9
正解です。追加するとlateinit、セッターの可視性でバッキングフィールドが公開されるため、KotlinとJavaからプロパティにアクセスする方法が異なります。また、Javaコードから、このプロパティはnullKotlinでのチェックなしでも設定できます。したがってlateinit、遅延初期化ではなく、必ずしもKotlinコードからの初期化ではありません。
マイケル

Swiftの「!」に相当するものはありますか??? 言い換えれば、それは遅れて初期化されるものですが、失敗することなくnullをチェックできます。'theObject == null'をオンにすると、Kotlinの 'lateinit'が「lateinitプロパティcurrentUser is not initialized」で失敗します。これは、コアの使用シナリオでnullでないオブジェクトがあり(したがって、null以外の抽象化に対してコード化したい)、非常に便利ですが、例外的/制限されたシナリオ(つまり、現在ログにアクセスしている場合)ではnullですユーザーの場合、最初のログイン時/ログイン画面を除いてnullになることはありません)
Marchy

@Marchy、明示的に保存されたLazy+ .isInitialized()を使用してそれを行うことができます。あなたがそこからnull得ることができないという保証のために、そのようなプロパティをチェックする簡単な方法はないと思いますnull。:) このデモを参照してください。
ホットキー2017

@hotkeyあまりにも多く使用by lazyすると、ビルド時間やランタイムが遅くなる可能性がありますか?
Dr.jacky

lateinit使用してnull、初期化されていない値の使用を回避するというアイデアが気に入りました。それ以外nullは絶対に使用しないでくださいlateinit。ヌルは削除できます。それが私がKotlinを愛する方法です:)
KenIchi

26

さらにhotkeyの良い答えに加えて、実際に私が2つの中から選択する方法を次に示します。

lateinit 外部初期化用です。メソッドを呼び出して値を初期化するために外部のものが必要な場合。

例:

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

一方lazy、オブジェクト内部の依存関係のみを使用する場合です。


1
外部オブジェクトに依存していても、遅延初期化は可能だと思います。値を内部変数に渡すだけです。そして、遅延初期化中に内部変数を使用します。しかし、それはLateinitと同じくらい自然です。
Elye、2016年

このアプローチはUninitializedPropertyAccessExceptionをスローします。値を使用する前にセッター関数を呼び出していることを再確認しました。lateinitで欠けている特定のルールはありますか?あなたの答えで、MyClassとAnyをAndroidコンテキストに置き換えてください。
Talha

24

非常に短く簡潔な答え

lateinit:null以外のプロパティを最近初期化します

遅延初期化とは異なり、lateinitを使用すると、null以外のプロパティの値がコンストラクターステージに格納されていないことをコンパイラーが認識して、正常にコンパイルできます。

遅延初期化

Kotlinで遅延初期化を実行する読み取り専用(val)プロパティを実装する場合、by by lazyは非常に便利です。

by lazy {...}は、宣言ではなく、定義されたプロパティが最初に使用されるイニシャライザを実行します。


すばらしい回答、特に「定義ではなく、定義されたプロパティが最初に使用されるイニシャライザを実行する」
user1489829

17

レイトイニーvsレイジー

  1. 遅い

    i)可変変数[var]で使用する

    lateinit var name: String       //Allowed
    lateinit val name: String       //Not Allowed

    ii)null不可のデータ型のみで許可

    lateinit var name: String       //Allowed
    lateinit var name: String?      //Not Allowed

    iii)値が将来的に初期化されることをコンパイラに約束します。

lateinit変数に初期化せずにアクセスしようとすると、UnInitializedPropertyAccessExceptionがスローされます。

  1. 怠惰な

    i)遅延初期化は、オブジェクトの不要な初期化を防ぐために設計されました。

    ii)使用しない限り、変数は初期化されません。

    iii)一度だけ初期化されます。次回使用するときは、キャッシュメモリから値を取得します。

    iv)スレッドセーフです(最初に使用されるスレッドで初期化されます。他のスレッドはキャッシュに格納されている同じ値を使用します)。

    v)変数はvalのみです。

    vi)変数はnull不可にのみできます。


7
遅延変数ではvarは使用できないと思います。
デニッシュシェルマ

4

すべてのすばらしい答えに加えて、遅延読み込みと呼ばれる概念があります。

遅延読み込みは、オブジェクトの初期化を必要な時点まで延期するためにコンピュータプログラミングで一般的に使用される設計パターンです。

適切に使用すると、アプリケーションのロード時間を短縮できます。そして、その実装のKotlinの方法はlazy()、必要なときにいつでも必要な値を変数にロードすることです。

しかし、lateinitは、変数がnullまたは空ではなく、それを使用する前に初期化されることが確実な場合に使用されますonResume()


はい、私はまたで初期化onCreateViewonResumeおよびと他のlateinitが、(一部のイベントが早く開始するので)時にエラーが発生しました。したがって、おそらくby lazy適切な結果が得られます。lateinitライフサイクル中に変更される可能性のある非null変数に使用します。
CoolMind

2

上記はすべて正しいですが、事実の簡単な説明の 1つLAZY ----最初の使用までオブジェクトのインスタンスの作成を遅らせたい場合があります。この手法は、遅延初期化または遅延インスタンス化と呼ばれます。遅延初期化の主な目的は、パフォーマンスを向上させ、メモリフットプリントを削減することです。タイプのインスタンスのインスタンス化に大きな計算コストがかかり、プログラムが実際にそれを使用しない場合は、CPUサイクルの浪費を遅らせたり、回避したりすることもできます。


0

Springコンテナを使用していて、null不可のBeanフィールドを初期化したい場合lateinitは、より適しています。

    @Autowired
    lateinit var myBean: MyBean

1
次のようにする必要があります@Autowired lateinit var myBean: MyBean
Cnfn 2018年

0

変更できない変数を使用する場合は、by lazy { ... }またはで初期化することをお勧めしますval。この場合、必要なときに最大で1回、常に初期化されることが保証されます。

null以外の変数が必要な場合は、その値を変更できますlateinit var。Androidの開発では、後でのような、このようなイベントでそれを初期化することができonCreateonResume。RESTリクエストを呼び出してこの変数にアクセスUninitializedPropertyAccessException: lateinit property yourVariable has not been initializedすると、その変数が初期化できるよりも速くリクエストが実行される可能性があるため、例外が発生する可能性があることに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.