关于属性:Kotlin – 使用“by lazy”与“lateinit”进行属性初始化

Kotlin - Property initialization using “by lazy” vs. “lateinit”

在Kotlin中,如果不想在构造函数内或在类体顶部初始化类属性,则基本上有这两个选项(从语言引用中):

  • 延迟初始化
  • lazy() is a function that takes a lambda and returns an instance of Lazy which can serve as a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result, subsequent calls to get() simply return the remembered result.

    Example

    1
    2
    3
    4
    5
    public class Hello {

       val myLazyString: String by lazy {"Hello" }

    }

    所以第一次呼叫和随后的呼叫,无论在哪里,到mylazystring都会返回"你好"

  • 初始化延迟
  • Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

    To handle this case, you can mark the property with the lateinit modifier:

    1
    2
    3
    4
    5
    6
    7
    8
    public class MyTest {

       lateinit var subject: TestSubject

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

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

    The modifier can only be used on var properties declared inside the body of a class (not in the primary constructor), and only when the property does not have a custom getter or setter. The type of the property must be non-null, and it must not be a primitive type.

    那么,如何在这两个选项之间做出正确的选择,因为两者都能解决同样的问题?


    以下是lateinit varby lazy { ... }委托财产之间的显著差异:

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

    • lateinit var有一个存储值的支持字段,by lazy { ... }创建了一个委托对象,在该对象中,一旦计算出值,就会将对委托实例的引用存储在类对象中,并为与委托实例一起使用的属性生成getter。因此,如果需要类中存在的支持字段,请使用lateinit

    • 除了EDOCX1·3的S,EDOCX1·4)不能用于可空属性和Java基元类型(这是因为EDCOX1引用12用于未初始化值);

    • lateinit var可以从任何可以看到对象的地方初始化,例如从框架代码内部初始化,并且单个类的不同对象可能有多个初始化场景。by lazy { ... }反过来定义了该属性的唯一初始值设定项,只有通过重写子类中的属性才能更改该初始值设定项。如果您希望以一种可能事先未知的方式从外部初始化属性,请使用lateinit

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

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

    • 如果您持有对lazy实例的引用,那么isInitialized()允许您检查该实例是否已初始化(并且您可以通过委托属性的反射来获取该实例)。要检查lateinit属性是否已初始化,可以使用property::isInitialized,因为kotlin 1.2。

    • 传递给by lazy { ... }的lambda可以捕获来自上下文的引用,在上下文中使用它来结束它。然后,它将存储引用,并仅在属性初始化后释放它们。这可能导致对象层次结构,如Android活动,不会被释放太长时间(或者,如果属性保持可访问性并且从未被访问过),因此您应该注意在初始值设定项lambda中使用什么。

    另外,在该问题中没有提到另一种方式:EDCOX1(26),它适合于延迟非空属性的初始化,包括Java原始类型的初始化。


    除了EDOCX1·27的好答案,以下是我在实践中如何选择:

    EDCOX1 4是外部初始化:当你需要外部的东西来初始化你的价值,通过调用一个方法。

    例如通过呼叫:

    1
    2
    3
    4
    5
    private lateinit var value: MyClass

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

    lazy只使用对象内部的依赖项。


    非常简短的回答

    lateInit:它最近初始化了非空属性

    与延迟初始化不同,lateInit允许编译器识别非空属性的值没有存储在要正常编译的constructor阶段。

    延迟初始化

    当实现在Kotlin中执行惰性初始化的只读(val)属性时,by Lazy可能非常有用。

    通过懒惰{…}在首次使用已定义属性的位置执行其初始值设定项,而不是其声明。


    除了所有伟大的答案外,还有一个称为懒惰加载的概念:

    Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed.

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

    但是,当您确定某个变量不会为空或为空时,就会使用lateinit,并且在使用它之前会对其进行初始化,例如在Android的onResume()方法中,因此您不希望将其声明为可为空的类型。


    信贷转到@amit shekhar

    拉蒂尼特

    Lateinit是延迟初始化。

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Test {

      lateinit var mock: Mock

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

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

    懒惰的

    懒惰是懒惰的初始化。

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

    例子:

    1
    2
    3
    public class Example{
      val name: String by lazy {"Amit Shekhar" }
    }


    lateinit示例(什么是后期初始化):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Late {

        lateinit var mercedes: Mercedes

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

    lazy示例(什么是延迟初始化):

    1
    2
    3
    4
    public class Lazy {

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


    如果您使用的是Spring容器,并且想要初始化不可为空的bean字段,那么lateinit更适合。

    1
    2
        @Autowired
        lateinit var myBean: MyBean