繁体   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