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

时间:2016-04-14 12:30:00

标签: properties kotlin

在Kotlin中如果你不想在构造函数或类体顶部初始化类属性,你基本上有这两个选项(来自语言参考):

  1. Lazy Initialization
  2.   

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

         

    示例

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

    因此,第一次调用以及无论在何处调用 myLazyString 的顺序调用都将返回“Hello”

    1. Late Initialization
    2.   

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

           

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

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

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

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

11 个答案:

答案 0 :(得分:231)

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

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

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

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

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

  • 默认情况下,初始化lateinit var是线程安全的,并保证最多只调用一次初始化程序(但可以使用another lazy overload更改)。在Lazy的情况下,用户的代码可以在多线程环境中正确初始化属性。

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

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

  • 传递给function Wrapper(delay) { var isCleared, intervalId, intervalDelay = delay || 5e3; // default delay of 5 sec function clear() { if (!isCleared) { console.log('clearing interval'); isCleared = true; clearInterval(intervalId); } } function setUpInterval(callback){ var params = [].slice.call(arguments, 1); if (!callback) { throw new Error('Callback for interval expected'); } params.unshift(intervalDelay); params.unshift(callback); intervalId = setInterval.apply(null, params); } return { setUp : setUpInterval, clear : clear } } function intervalCallback() { console.log([].slice.call(arguments).join(',')); } var wrapper = Wrapper(1e3); // create wrapper with delay for interval console.log('test case 1'); wrapper.setUp(intervalCallback, 'params', 'to', 'callback'); // call clear interval after 10sec setTimeout(function() { wrapper.clear(); }, 10e3); 的lambda可能会捕获从用于其closure的上下文中的引用。然后它将存储引用并仅在属性初始化后释放它们。这可能导致对象层次结构(例如Android活动)不会被释放太长时间(或者,如果属性仍然可访问且永远不会被访问),那么您应该注意在初始化程序lambda中使用的内容。

此外,问题中未提及的另一种方法是:Delegates.notNull(),它适用于非空属性的延迟初始化,包括Java基元类型的延迟初始化。

答案 1 :(得分:17)

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

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

e.g。致电:

private lateinit var value: MyClass

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

虽然lazy是仅使用对象内部的依赖项。

答案 2 :(得分:8)

非常简短的答案

lateinit:最近初始化非空属性

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

延迟初始化

在实现在Kotlin中执行延迟初始化的 只读 (val)属性时,

by lazy 可能非常有用。

由lazy {...}执行其初始化程序,其中首次使用已定义的属性,而不是其声明。

答案 3 :(得分:2)

积分转到@Amit Shekhar

<强> lateinit

lateinit是迟到的初始化。

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

示例:

public class Test {

  lateinit var mock: Mock

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

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

<强>懒惰

lazy是懒惰的初始化。

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

示例:

get()

答案 4 :(得分:2)

除了所有出色的答案外,还有一个概念称为延迟加载:

  

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

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

但是,当您确定某个变量不会为null或为空,并且在使用该变量之前会对其进行初始化时,可以使用lateinit -e.g.在适用于android-的onResume()方法中,因此您不想将其声明为可为null的类型。

答案 5 :(得分:2)

上面的一切都是正确的,但事实之一简单的解释 LAZY ----在某些情况下,您希望将对象实例的创建延迟到它的 第一次使用。此技术称为延迟初始化或延迟实例化。主要的 延迟初始化的目的是提高性能并减少内存占用。如果 实例化您的类型的实例需要大量的计算量,并且该程序 可能最终没有真正使用它,您可能要延迟甚至避免浪费CPU 循环。

答案 6 :(得分:2)

顺便提一下,lateinit和lazy的区别

lateinit

  1. 仅用于可变变量,即 var 和不可为 null 的数据类型
<块引用>

lateinit var name: String //允许不可为空

  1. 您告诉编译器该值将在将来被初始化。

注意:如果您尝试访问 lateinit 变量而不对其进行初始化,则会抛出 UnInitializedPropertyAccessException。

懒惰

  1. 延迟初始化旨在防止对对象进行不必要的初始化。

  2. 除非您使用它,否则您的变量不会被初始化。

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

  4. 它是线程安全的。

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

干杯:)

答案 7 :(得分:0)

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

    @Autowired
    lateinit var myBean: MyBean

答案 8 :(得分:0)

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

public class Late {

    lateinit var mercedes: Mercedes

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

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

public class Lazy {

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

答案 9 :(得分:0)

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

如果要使用非null变量(可以更改其值),请使用lateinit var。在Android开发中,您以后可以在诸如onCreateonResume之类的事件中对其进行初始化。请注意,如果调用REST请求并访问此变量,则可能会导致异常UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized,因为该请求的执行速度比该变量初始化的速度快。

答案 10 :(得分:0)

lateinit vs lazy

  1. 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. 懒惰

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

    ii)除非使用变量,否则不会对其进行初始化。

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

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

    v)变量可以是 var val

    vi)变量可以为可为空或不可为可为空