属性初始化使用"by lazy"与“lateinit"

在Kotlin中,如果你不想在构造函数内部或类主体顶部初始化一个类属性,你基本上有以下两个选项(来自语言引用):

  1. 延迟初始化

lazy()是一个接受lambda并返回Lazy<T>实例的函数,该实例可以作为实现lazy属性的委托:对get()的第一次调用执行传递给lazy()的lambda并记住结果,对get()的后续调用只是返回记住的结果。

例子

public class Hello {


val myLazyString: String by lazy { "Hello" }


}

因此,myLazyString的第一次调用和后续调用,无论它在哪里,都将返回Hello

  1. 延迟初始化 . rref ="https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties" rel="noreferrer">

通常,声明为具有非空类型的属性必须在构造函数中初始化。然而,这通常并不方便。例如,属性可以通过依赖注入初始化,或者在单元测试的设置方法中初始化。在这种情况下,不能在构造函数中提供非空初始化式,但在引用类主体中的属性时仍然希望避免空检查。

要处理这种情况,你可以用lateinit修饰符标记属性:

public class MyTest {
   

lateinit var subject: TestSubject


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


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

修饰符只能用在类主体内部声明的var属性上(不能用在主构造函数中),且仅当该属性没有自定义getter或setter时使用。属性的类型必须是非空的,并且不能是基本类型。

那么,既然这两种方法都能解决同一个问题,如何在这两种方法中正确选择呢?

202052 次浏览

下面是lateinit varby lazy { ... }委托属性之间的显著区别:

  • lazy { ... }委托只能用于val属性,而lateinit只能应用于vars,因为它不能编译为final字段,因此不能保证不变性;

  • lateinit var有一个存储值的后台字段,并且by lazy { ... }创建了一个委托对象,其中存储了计算后的值,在类对象中存储了对委托实例的引用,并为与委托实例一起工作的属性生成getter。所以如果你需要类中的支持字段,使用lateinit;

  • 除了__abc0之外,lateinit不能用于可空属性或Java原语类型(这是因为null用于未初始化的值);

  • lateinit var可以从对象可见的任何地方初始化,例如在框架代码中,并且对于单个类的不同对象可以有多种初始化场景。反过来,by lazy { ... }定义了属性的唯一初始化式,它只能通过重写子类中的属性来更改。如果你想要你的属性从外部以一种可能事先未知的方式初始化,使用lateinit

  • by lazy { ... }在默认情况下是线程安全的,并保证初始化式最多被调用一次(但这可以通过使用另一个lazy重载来改变)。在lateinit var的情况下,在多线程环境中正确初始化属性取决于用户的代码。

  • Lazy实例可以保存、传递甚至用于多个属性。相反,__abc1不存储任何额外的运行时状态(对于未初始化的值,字段中只有null)。

  • 如果你持有对Lazy实例的引用,isInitialized()允许你检查它是否已经初始化(你可以从委托属性通过反射获得这样的实例)。要检查lateinit属性是否已初始化,可以使用从Kotlin 1.2开始使用property::isInitialized

  • 传递给by lazy { ... }的lambda可以捕获上下文中的引用,该上下文将它使用到它的关闭..然后,它将存储引用,并仅在属性初始化后释放它们。这可能会导致对象层次结构,如Android活动,不会被释放太长时间(或者,如果属性仍然可访问,并且永远不会被访问),所以你应该小心你在初始化式lambda中使用的内容。

此外,还有另一种方法没有在问题中提到:Delegates.notNull(),它适用于延迟初始化非空属性,包括Java原语类型的属性。

除了hotkey的好答案,下面是我如何在实践中选择这两个:

lateinit用于外部初始化:当你需要通过调用方法来初始化你的值时。

例如,通过呼叫:

private lateinit var value: MyClass


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

lazy是当它只使用对象内部的依赖项时。

如果你正在使用Spring容器并且你想初始化不可空的bean字段,lateinit更适合。

    @Autowired
lateinit var myBean: MyBean

非常简短和简洁的回答

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

与延迟初始化不同,lateinit允许编译器识别非空属性的值没有存储在构造函数阶段以正常编译。

延迟初始化

在Kotlin中实现执行延迟初始化的< em >只读< / em >(val)属性时,通过懒惰可能非常有用。

懒人{…}在第一次使用已定义属性的地方执行其初始化式,而不是其声明。

除了这些很棒的答案,还有一个概念叫做惰性加载:

延迟加载是计算机编程中常用的一种设计模式,用于将对象的初始化推迟到需要它的时候。

正确使用它,可以减少应用程序的加载时间。Kotlin的实现方式是通过lazy(),它在需要的时候将所需的值加载到变量中。

但是lateinit是在你确定一个变量不会为空或空,并且在你使用它之前会被初始化时使用的——例如在android的onResume()方法中——所以你不想将它声明为可空类型。

如果使用不可更改的变量,则最好使用by lazy { ... }val进行初始化。在这种情况下,您可以确保它总是在需要时初始化,最多初始化一次。

如果你想要一个非空变量,可以改变它的值,使用lateinit var。在Android开发中,你可以稍后在诸如onCreateonResume这样的事件中初始化它。注意,如果调用REST请求并访问这个变量,可能会导致异常UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized,因为请求的执行速度比变量初始化的速度快。

Lateinit vs lazy

  1. < p > lateinit

    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. < p > 懒惰的

    i)延迟初始化是为了防止不必要的对象初始化。

ii)你的属性不会被初始化,除非你使用它。

iii)只初始化一次。下次使用它时,将从缓存中获取值。

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

v)属性只能是瓦尔

vi)属性可以是任何类型(包括基本类型和空值,这在lateinit中是不允许的)。

以上所有内容都是正确的,但事实之一简单的解释 懒惰的----在某些情况下,当你想延迟对象的实例的创建,直到它 第一次使用。这种技术称为延迟初始化或延迟实例化。主要的 延迟初始化的目的是提高性能并减少内存占用。如果 实例化您的类型的实例会带来很大的计算成本和程序 可能最终不会真正使用它,你会想要延迟甚至避免浪费CPU 周期。< / p >

顺便说一下lateinit和lazy

lateinit

  1. 仅用于可变变量,即var和非空数据类型

lateinit var name: String //允许非空

  1. 你是在告诉编译器这个值将来会被初始化。

注意:如果你试图访问lateinit变量而没有初始化它,那么它会抛出UnInitializedPropertyAccessException异常。

懒惰的

  1. 延迟初始化是为了防止不必要的对象初始化而设计的。

  2. 你的变量将不会被初始化,除非你使用它。

  3. 只初始化一次。下次使用它时,将从缓存中获取值。

  4. 线程安全。

  5. 该变量只能为val且不可为空。

欢呼:)