web-dev-qa-db-ja.com

Kotlin - "by lazy"と "lateinit"を使ったプロパティの初期化

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

  1. 遅延初期化

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

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

そのため、myLazyStringに対する最初の呼び出しと副次呼び出しは、どこにあっても "Hello"を返します。

  1. 後期初期化

通常、null以外の型を持つと宣言されたプロパティは、コンストラクタで初期化する必要があります。しかし、かなり頻繁にこれは便利ではありません。たとえば、依存関係の注入や単体テストの設定方法でプロパティを初期化できます。この場合、コンストラクタにnull以外の初期化子を指定することはできませんが、それでもクラスの本体内でプロパティを参照するときにnullチェックを回避する必要があります。

この場合を処理するために、プロパティにlateinit修飾子を付けます。

public class MyTest {

   lateinit var subject: TestSubject

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

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

修飾子は、クラスの本体の内部で宣言された(プライマリコンストラクタ内ではない)varプロパティで、そのプロパティにカスタムのgetterまたはsetterがない場合にのみ使用できます。プロパティの型はnull以外でなければならず、またプリミティブ型であってはなりません。

それで、これらの2つのオプションのどちらを正しく選択するか、それらの両方が同じ問題を解決することができるので。

194
regmoraes

これがlateinit varby lazy { ... }の委譲プロパティの大きな違いです。

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

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

  • valsに加えて、lateinitはnull許容プロパティやJavaプリミティブ型には使用できません(これはnullが未初期化値に使用されるためです)。

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

  • 初期化by lazy { ... }はデフォルトでスレッドセーフであり、初期化子はせいぜい一度だけ呼び出されることを保証します(しかしこれは 別のlazyオーバーロード を使って変更することができます)。 lateinit varの場合、マルチスレッド環境でプロパティを正しく初期化するのはユーザーのコード次第です。

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

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

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

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

237
hotkey

hotkeyの良い答えに加えて、ここに私が実際に2つの中から選ぶ方法があります:

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

例えば呼び出して:

private lateinit var value: MyClass

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

lazyはあなたのオブジェクトの内部の依存関係だけを使うときです。

17
Guillaume

とても短くて簡潔な答え

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

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

lazy初期化

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

by lazy {...}は、宣言ではなく、定義済みプロパティが最初に使用される場所で初期化を実行します。

8
John Wick

クレジットは @ Amit Shekhar

lateinit

lateinitは遅延初期化です。

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

例:

public class Test {

  lateinit var mock: Mock

  @SetUp fun setup() {
     mock = Mock()
  }

  @Test fun test() {
     mock.do()
  }
}

lazy

lazyは遅延初期化です。

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

例:

public class Example{
  val name: String by lazy { “Amit Shekhar” }
}
3
Dhaval Jivani

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

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

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

しかし、変数がnullまたは空にならず、使用する前に初期化されることが確実な場合は、lateinitを使用します。 Android-のonResume()メソッドでは、nullを許容される型として宣言したくありません。

2

lateinit vs lazy

  1. lateinit

    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)将来、値が初期化されることがコンパイラーに約束されています。

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

  1. 遅延

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

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

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

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

    v)変数はvarまたはvalにできます。

    vi)変数はnullableまたはnon -nullableにできます。

2
Geeta Gupta

lateinitの例(遅い初期化とは):

public class Late {

    lateinit var mercedes: Mercedes

    @SetUp fun setup() {
        mercedes = Mercedes()
    }
    @Test fun testing() {
        mercedes.do()
    }
}

lazyの例(遅延初期化とは)

public class Lazy {

    val name: String by lazy { "Mercedes-Benz" }
}
0
ARGeo

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

    @Autowired
    lateinit var myBean: MyBean
0
mpprdev

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

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

0
CoolMind