簡體   English   中英

使用“by lazy”與“lateinit”進行屬性初始化

[英]Property initialization using "by lazy" vs. "lateinit"

在 Kotlin 中,如果您不想在構造函數內部或 class 主體的頂部初始化 class 屬性,您基本上有以下兩個選項(來自語言參考):

  1. 惰性初始化

lazy()是一個 function,它接受一個 lambda 並返回一個Lazy<T>實例,它可以作為實現惰性屬性的委托:第一次調用get()執行傳遞給lazy()的 lambda 並記住結果,后續調用get()只會返回記住的結果。

例子

public class Hello { val myLazyString: String by lazy { "Hello" } }

因此,無論在哪里,對myLazyString的第一次調用和后續調用都會返回Hello

  1. 延遲初始化

通常,聲明為具有非空類型的屬性必須在構造函數中初始化。 然而,這通常並不方便。 例如,可以通過依賴注入或單元測試的設置方法來初始化屬性。 在這種情況下,您不能在構造函數中提供非空初始化程序,但在引用 class 主體內的屬性時,您仍然希望避免 null 檢查。

要處理這種情況,您可以使用 lateinit 修飾符標記該屬性:

 public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } }

該修飾符只能用於在 class 主體內聲明的 var 屬性(而不是在主構造函數中),並且僅當該屬性沒有自定義 getter 或 setter 時。 屬性的類型必須是非空的,並且不能是原始類型。

那么,既然兩者都能解決同一個問題,如何在這兩個選項之間做出正確的選擇呢?

以下是lateinit varby lazy { ... }委托屬性之間的顯着差異:

  • lazy { ... }委托只能用於val屬性,而lateinit只能用於var s,因為它不能編譯為final字段,因此無法保證不變性;

  • lateinit var有一個存儲值的支持字段,並by lazy { ... }創建一個委托對象,一旦計算出該值就存儲在該對象中,將委托實例的引用存儲在類對象中,並為該屬性生成 getter與委托實例一起使用。 因此,如果您需要類中存在的支持字段,請使用lateinit

  • 除了val之外, lateinit不能用於可空屬性或 Java 原始類型(這是因為null用於未初始化的值);

  • lateinit var可以從任何可以看到對象的地方初始化,例如從框架代碼內部,對於單個類的不同對象可能有多個初始化場景。 by lazy { ... } ,反過來,定義屬性的唯一初始化器,只能通過覆蓋子類中的屬性來更改它。 如果您希望您的屬性以一種可能事先未知的方式從外部初始化,請使用lateinit

  • 默認情況下, by lazy { ... }初始化是線程安全的,並保證初始化程序最多被調用一次(但這可以通過使用 另一個lazy重載來更改)。 對於lateinit var ,在多線程環境中正確初始化屬性取決於用戶的代碼。

  • Lazy實例可以保存、傳遞甚至用於多個屬性。 相反, lateinit var不存儲任何額外的運行時狀態(未初始化值的字段中只有null )。

  • 如果您持有對Lazy實例的引用,則isInitialized()允許您檢查它是否已被初始化(並且您可以通過委托屬性的反射獲取此類實例)。 要檢查是否已初始化 lateinit 屬性,您可以使用自 Kotlin 1.2 以來的property::isInitialized

  • 傳遞給by lazy { ... } lambda 可以從上下文中捕獲引用,並將其用於其閉包中.. 然后它將存儲引用並僅在屬性初始化后釋放它們。 這可能會導致對象層次結構(例如 Android 活動)不會被釋放太長時間(或者永遠不會釋放,如果該屬性仍然可訪問且從未被訪問過),因此您應該小心在初始化程序 lambda 中使用的內容。

此外,問題中還有另一種方法沒有提到: Delegates.notNull() ,它適用於非空屬性的延遲初始化,包括 Java 基本類型的屬性。

延遲初始化 vs 懶惰

  1. 延遲初始化

    i) 與可變變量 [var] 一起使用

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

ii) 僅允許不可為空的數據類型

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

iii) 向編譯器承諾該值將在未來被初始化。

注意:如果您嘗試訪問lateinit變量而不初始化它,那么它會拋出 UnInitializedPropertyAccessException。

  1. 懶惰的

    i) 延遲初始化旨在防止對象的不必要初始化。

ii) 你的變量不會被初始化,除非你使用它。

iii) 它只被初始化一次。 下次使用它時,您會從緩存中獲取值。

iv)它是線程安全的(它在第一次使用它的線程中初始化。其他線程使用存儲在緩存中的相同值)。

v) 變量只能是val

vi) 變量只能是不可為空的

除了hotkey的好答案之外,以下是我在實踐中如何在兩者中進行選擇:

lateinit用於外部初始化:當您需要外部內容通過調用方法來初始化您的值時。

例如通過調用:

private lateinit var value: MyClass

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

lazy是指它只使用對象內部的依賴項。

非常簡短的答案

lateinit:它最近初始化非空屬性

與延遲初始化不同, lateinit允許編譯器識別非空屬性的值未存儲在構造函數階段以正常編譯。

延遲初始化

在 Kotlin 中實現執行惰性初始化的只讀(val) 屬性時, by lazy可能非常有用。

by lazy { ... } 在第一次使用定義的屬性的地方執行它的初始化程序,而不是它的聲明。

除了所有出色的答案之外,還有一個稱為延遲加載的概念:

延遲加載是計算機編程中常用的一種設計模式,用於將對象的初始化推遲到需要的時候。

正確使用它,您可以減少應用程序的加載時間。 Kotlin 的實現方式是通過lazy()將所需的值加載到您的變量中。

但是當您確定變量不會為空或為空並且將在您使用它之前初始化時使用 lateinit - 例如在 android 的onResume()方法中 - 因此您不想將其聲明為可空類型。

順便說一下,lateinit 和lazy 的區別

延遲初始化

  1. 僅用於可變變量,即 var 和不可為空的數據類型

lateinit var name: String //允許不可為空

  1. 您告訴編譯器該值將在將來被初始化。

注意:如果您嘗試訪問 lateinit 變量而不初始化它,那么它會拋出 UnInitializedPropertyAccessException。

懶惰的

  1. 延遲初始化旨在防止對對象進行不必要的初始化。

  2. 除非您使用它,否則您的變量不會被初始化。

  3. 它只初始化一次。 下次使用它時,您會從緩存中獲取值。

  4. 它是線程安全的。

  5. 變量只能是 val 且不可為空。

干杯:)

上面一切都是正確的,但一個事實簡單解釋LAZY ----在某些情況下,您希望將對象實例的創建延遲到第一次使用。 這種技術稱為惰性初始化或惰性實例化。 延遲初始化的主要目的是提高性能並減少內存占用。 如果實例化您的類型的實例需要大量的計算成本並且程序最終可能不會實際使用它,您可能希望延遲甚至避免浪費 CPU 周期。

歸功於@Amit Shekhar

lateinit

lateinit是遲到的初始化。

通常,必須在構造函數中初始化聲明為具有非null類型的屬性。 但是,這通常不方便。 例如,可以通過依賴注入或單元測試的設置方法初始化屬性。 在這種情況下,您無法在構造函數中提供非null初始值設定項,但在引用類體內的屬性時仍希望避免空值檢查。

例:

public class Test {

  lateinit var mock: Mock

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

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

懶惰是初始化懶惰。

lazy()是一個函數,它接受一個lambda並返回一個lazy實例,它可以作為實現一個惰性屬性的委托:第一次調用get()執行傳遞給lazy()的lambda並記住結果,后續調用get()只返回記住的結果。

例:

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

如果您使用的是 Spring 容器並且想要初始化不可為 null 的 bean 字段, lateinit適合使用lateinit

    @Autowired
    lateinit var myBean: MyBean

如果您使用不可更改的變量,那么最好by lazy { ... }val進行初始化。 在這種情況下,您可以確保它總是在需要時初始化,最多 1 次。

如果您想要一個可以更改其值的非空變量,請使用lateinit var 在 Android 開發中,您可以稍后在諸如onCreateonResume類的事件中對其進行初始化。 請注意,如果您調用 REST 請求並訪問此變量,則可能會導致異常UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized ,因為請求的執行速度比該變量的初始化速度快。

LateinitLazy初始化是 Kotlin 語言中的兩個初始化屬性。

何時使用 Lateinit

  • 遲到初始化一個變量。 當您確定要在使用之前初始化變量時。 使用 var 關鍵字。
  • 如果變量在后期發生變化,即變量是否可變。 Lateinit 變量可以在類中聲明。
  • Lateinit 在初始化之前不分配內存。

使用 Lateinit 時應避免什么

  • 使用 Lateinit 時,變量不能為空類型。

  • Lateinit 不能用於非原始數據類型,即 Long 和 int。

  • 如果您嘗試在未初始化的情況下訪問 Lateinit 變量,它將拋出一個異常,指出它未初始化或被正確訪問。

  • 它可以稍后被初始化

private lateinit var lateUri : Uri

何時使用延遲初始化

  • 在延遲初始化中,除非您調用/使用它,否則不會初始化您的變量。

  • Lazy 初始化將變量初始化一次; 然后在整個代碼中使用相同的值。

  • 它用於只讀屬性,因為始終使用相同的值變量。

  • 此初始化用於 val 屬性的情況。

  • 當變量被所有人共享時是首選
    初始化一次。

  • 當對象依賴於類內部的變量時,可以使用它。

使用 Lazy 初始化時應避免什么

  • 代碼在不確定的時間傳播到整個班級,這可能會導致混亂。

  • Lazy 對象在稍后訪問時返回先前初始化的值。

  • 延遲初始化在保留片段上使用時會導致內存泄漏,因為它持有對舊視圖的引用。

    val string: String by lazy {val text = "this value"}

Lateinit 與延遲初始化

  • 如果屬性沒有自定義的 setter 和 getter,則使用 Lateinit。 在多線程環境中,Lateinit 初始化依賴於用戶。
  • 延遲初始化是線程安全的。
  • Lateinit 只能與 var 一起使用。
  • 延遲初始化與 val 屬性一起使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM