1. ホーム
  2. properties

[解決済み] by lazy" と "lateinit" を使ったプロパティの初期化

2022-03-23 15:04:15

質問

Kotlinでは、コンストラクタ内やクラス本体の先頭でクラスのプロパティを初期化したくない場合、基本的に以下の2つの選択肢があります(言語リファレンスより)。

  1. 遅延初期化

lazy() のインスタンスを返す関数です。 Lazy<T> を最初に呼び出すと、遅延プロパティを実装するためのデリゲートとして機能します。 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 プロパティ (一次コンストラクタ内ではない) にのみ使用でき、そのプロパティがカスタムゲッターまたはカスタムセッターを持たない場合にのみ使用できます。プロパティの型は非 NULL でなければならず、プリミティブ型であってはなりません。

では、この2つの選択肢はどちらも同じ問題を解決することができるため、どのように選択するのが正しいのでしょうか?

どのように解決するのか?

以下は lateinit varby lazy { ... } デリゲートプロパティ

  • lazy { ... } デリゲートは val プロパティがあるのに対し lateinit にのみ適用可能です。 var にコンパイルすることができないためです。 final フィールドがあるため、不変性が保証されない。

  • lateinit var は値を格納するバッキングフィールドを持ち by lazy { ... } は、一度計算された値が格納されるデリゲートオブジェクトを生成し、クラスオブジェクトにデリゲートインスタンスへの参照を格納し、デリゲートインスタンスと連携するプロパティ用ゲッターを生成します。ですから、もしクラス内に存在するバッキングフィールドが必要な場合は lateinit ;

  • に加えて val s, lateinit は、NULL可能なプロパティやJavaのプリミティブ型には使用できません(これは null 初期化されていない値に使用される)。

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

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

  • A Lazy のインスタンスは、保存、受け渡し、さらには複数のプロパティに使用することができます。逆に lateinit var は、追加の実行時状態を保存しません( null を初期化されていない値のフィールドに置く)。

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

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

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