Kotlin では、コンストラクター内またはクラス本体の上部でクラス プロパティを初期化したくない場合は、基本的に次の 2 つのオプションがあります (言語リファレンスより)。
lazy()
Lazy<T>
は、ラムダを受け取り、遅延プロパティを実装するためのデリゲートとして機能できるインスタンスを返す関数です。get()
への最初の呼び出しでは、渡されたラムダを実行してlazy()
結果を記憶し、 への後続の呼び出しでは、get()
記憶された結果を単に返します。例
public class Hello { val myLazyString: String by lazy { "Hello" } }
したがって、最初の呼び出しとその後の呼び出しは、それがどこであってもmyLazyString
、Hello
通常、非 null 型として宣言されたプロパティは、コンストラクターで初期化する必要があります。ただし、多くの場合、これは便利ではありません。たとえば、プロパティは依存性注入を通じて、または単体テストのセットアップ メソッドで初期化できます。この場合、コンストラクターで非 null 初期化子を指定することはできませんが、クラスの本体内でプロパティを参照するときに null チェックを回避する必要があります。
このケースを処理するには、プロパティに lateinit 修飾子をマークします。
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } }
修飾子は、クラスの本体内 (プライマリ コンストラクター内ではない) で宣言された var プロパティでのみ使用でき、プロパティにカスタム ゲッターまたはセッターがない場合に限り使用できます。プロパティの型は null 以外でなければならず、プリミティブ型であってはなりません。
では、どちらも同じ問題を解決できるので、これら 2 つのオプションのどちらを正しく選択すればよいのでしょうか?
ベストアンサー1
委任されたプロパティlateinit var
との重要な違いは次のとおりです。by lazy { ... }
lazy { ... }
val
delegate はプロパティにのみ使用できますが、はフィールドにコンパイルできないため、不変性が保証されず、lateinit
にのみ適用できます。var
final
lateinit var
には、値を格納するバッキング フィールドがあり、by lazy { ... }
計算された値が格納されるデリゲート オブジェクトを作成し、デリゲート インスタンスへの参照をクラス オブジェクトに格納し、デリゲート インスタンスで機能するプロパティのゲッターを生成します。したがって、クラスにバッキング フィールドが必要な場合は、 を使用しますlateinit
。val
sに加えて、lateinit
null 許容プロパティまたは Java プリミティブ型には使用できません (これは、初期化されていない値に使用されるためですnull
)。lateinit var
は、オブジェクトが見える場所であればどこからでも、たとえばフレームワーク コード内からでも初期化できます。by lazy { ... }
また、単一クラスの異なるオブジェクトに対して複数の初期化シナリオが可能です。 は、プロパティの唯一の初期化子を定義します。これは、サブクラスでプロパティをオーバーライドすることによってのみ変更できます。プロパティを、おそらく事前には知られていない方法で外部から初期化したい場合は、 を使用しますlateinit
。初期化は
by lazy { ... }
デフォルトでスレッドセーフであり、初期化子が最大1回呼び出されることを保証します(ただし、これはもう一つのlazy
過負荷) の場合lateinit var
、マルチスレッド環境でプロパティを正しく初期化するのはユーザーのコード次第です。インスタンス
Lazy
は保存したり、渡したり、複数のプロパティに使用することもできます。一方、lateinit var
は追加の実行時状態を保存しません (null
初期化されていない値のフィールドにのみ保存されます)。のインスタンスへの参照を保持する場合
Lazy
、isInitialized()
すでに初期化されているかどうかを確認できます(そして反射的にそのような例を得る委任されたプロパティから)。lateinitプロパティが初期化されているかどうかを確認するには、property::isInitialized
Kotlin 1.2以降で使用。渡されたラムダは、
by lazy { ... }
それが使用されているコンテキストから参照をキャプチャすることができます。閉鎖.. 次に参照を保存し、プロパティが初期化された後にのみ解放します。これにより、Android アクティビティなどのオブジェクト階層が長時間解放されない可能性があります (プロパティがアクセス可能なままでアクセスされない場合は、解放されない可能性があります)。そのため、初期化子ラムダ内で何を使用するかについては注意が必要です。
また、質問には記載されていない別の方法もあります。Delegates.notNull()
これは、Java プリミティブ型を含む null 以外のプロパティの遅延初期化に適しています。