首页 文章

Kotlin - 使用“by lazy”与“lateinit”进行属性初始化

提问于
浏览
156

在Kotlin中,如果您不想在构造函数内部或类体顶部启动类属性,那么基本上这两个选项(来自语言参考):

lazy()是一个函数,它接受一个lambda并返回一个Lazy实例,它可以作为实现一个惰性属性的委托:第一次调用get()执行传递给lazy()的lambda并记住结果,随后调用get()只是返回记住的结果 . 示例公共类Hello {

val myLazyString:lazy {“Hello”}的字符串

}

所以第一次调用和顺序调用,无论它在哪里,到 myLazyString 将返回 "Hello"

通常,必须在构造函数中初始化声明为具有非null类型的属性 . 但是,这通常不方便 . 例如,可以通过依赖注入或单元测试的设置方法初始化属性 . 在这种情况下,您无法在构造函数中提供非null初始值设定项,但在引用类体内的属性时仍希望避免空值检查 . 要处理这种情况,可以使用lateinit修饰符标记属性:public class MyTest {

lateinit var subject:TestSubject

@SetUp fun setup()

@Test fun test(){subject.method()}
}
修饰符只能用于在类体(不在主构造函数中)内声明的var属性,并且只能在属性没有自定义getter或setter时使用 . 属性的类型必须为非null,并且它不能是基本类型 .

那么,如何在这两个选项之间正确选择,因为它们都可以解决同样的问题?

5 回答

  • 0

    如果您正在使用Spring容器并且想要初始化非可空bean字段,则 lateinit 更适合 .

    @Autowired
    lateinit var MyBean myBean
    
  • 184

    非常简短的答案

    lateinit: It initialize non-null properties lately

    与延迟初始化不同, lateinit 允许编译器识别非null属性的值未存储在构造函数阶段中以进行正常编译 .

    lazy Initialization

    在实现在Kotlin中执行延迟初始化的 read-only (val)属性时, by lazy 可能非常有用 .

    by lazy 执行其初始化程序,其中首先使用定义的属性,而不是其声明 .

  • 2

    Credit goes to @Amit Shekhar

    lateinit

    lateinit是迟到的初始化 .

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

    Example:

    public class Test {
    
      lateinit var mock: Mock
    
      @SetUp fun setup() {
         mock = Mock()
      }
    
      @Test fun test() {
         mock.do()
      }
    }
    

    lazy

    懒惰是初始化的延迟 .

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

    Example:

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

    除了 hotkey 的好答案之外,以下是我在实践中如何选择:

    lateinit 用于外部初始化:当您需要外部资源来通过调用方法初始化您的值时 .

    例如致电:

    private lateinit var value: MyClass
    
    fun init(externalProperties: Any) {
       value = somethingThatDependsOn(externalProperties)
    }
    

    虽然 lazy 是它只使用对象内部的依赖项 .

  • 16

    以下是 lateinit varby lazy { ... } 委托属性之间的显着差异:

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

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

    • 除了 val s之外, lateinit 不能用于可空属性和Java基元类型(这是因为 null 用于未初始化的值);

    • lateinit var 可以从看到对象的任何地方初始化,例如,从框架代码内部,可以为单个类的不同对象提供多个初始化方案 . 反过来, by lazy { ... } 定义了属性的唯一初始化程序,只能通过覆盖子类中的属性来更改 . 如果您希望以事先未知的方式从外部初始化您的属性,请使用 lateinit .

    • 初始化 by lazy { ... } 默认是线程安全的,并保证最多调用初始化程序一次(但这可以通过使用another lazy overload来改变) . 在 lateinit var 的情况下,它是在多线程环境中正确初始化属性的's up to the user'代码 .

    • Lazy 实例可以保存,传递甚至用于多个属性 . 相反, lateinit var s不存储任何其他运行时状态(在未初始化值的字段中仅为 null ) .

    • 如果您持有对 Lazy 实例的引用,isInitialized()允许您检查它是否已经初始化(并且您可以从委托属性中obtain such instance with reflection) . 要检查lateinit属性是否已初始化,您可以use property::isInitialized since Kotlin 1.2 .

    • 传递给 by lazy { ... } 的lambda可以捕获从它被用于closure的上下文中的引用 . 然后它将存储引用并仅在属性初始化后释放它们 . 这可能导致对象层次结构(例如Android活动)不会被释放太长时间(或者,如果属性仍然可访问且永远不会被访问),那么您应该注意在初始化程序lambda中使用的内容 .

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

相关问题