Kotlin进阶实战
上QQ阅读APP看书,第一时间看更新

5.3 lateinit和by lazy

Kotlin基于Java的空指针提出了一个空安全的概念,即每个属性默认不可为null。在某个类中,如果某些成员变量没办法在一开始就初始化,并且又不想使用可空类型(也就是带“?”的类型)。那么,可以使用lateinit或者by lazy来修饰它。

被lateinit修饰的变量并不是不用初始化,它需要在生命周期流程中进行获取或者初始化。

而lazy()是一个函数,可以接收一个Lambda表达式作为参数,第一次调用时会执行Lambda表达式,以后调用该属性会返回之前的结果。

by lazy是Kotlin属性委托中的延迟属性。例如下面的代码:

执行结果如下:

     aaron
     cafei
     tony
     -----------
     tony

因为lazy()最后一行返回的值即为str的值,所以以后每次调用str都可以直接返回该值。

下面分析by lazy的源码。

从lazy()开始分析源码:

actual是Kotlin的关键字,表示多平台项目中的一个平台相关实现。

lazy函数的参数是initializer,它是一个函数类型。lazy函数会创建一个SynchronizedLazyImpl类,并传入initializer参数。

下面是SynchronizedLazyImpl的源码:

可以看到SynchronizedLazyImpl实现了Lazy、Serializable接口,它的value属性重载了Lazy接口的value。

Lazy接口的value属性用于获取当前Lazy实例的延迟初始化值。一旦初始化后,它不得在此Lazy实例的剩余生命周期内更改。

所以SynchronizedLazyImpl的value属性只有get()方法,没有set()方法。

value的get()方法会先判断SynchronizedLazyImpl的_value属性是否是UNINITIALIZED_VALUE,不是的话会返回_value的值。

_value使用@Volatile注解标注,相当于在Java中使用volatile修饰_value属性。volatile具有可见性、有序性,因此一旦_value的值修改了,其他线程可以看到其最新的值。

SynchronizedLazyImpl的_value属性存储了initializer的值。

如果_value的值等于UNINITIALIZED_VALUE,则调用initializer来获取值,通过synchronized来保证这个过程是线程安全的。

lazy()方法还有一个实现,比起上面的方法多了一个参数类型LazyThreadSafetyMode。

SYNCHRONIZED使用的是SynchronizedLazyImpl,跟之前分析的lazy()方法是一致的,PUBLICATION使用的是SafePublicationLazyImpl,而NONE使用的是UnsafeLazyImpl。

其中,UnsafeLazyImpl不是线程安全的,而其他都是线程安全的。

SafePublicationLazyImpl使用AtomicReferenceFieldUpdater来保证_value属性的原子操作。毕竟,volatile不具备原子性。

因此,SafePublicationLazyImpl支持同时多个线程调用,并且可以在全部或部分线程上同时进行初始化。但是,如果某个值已由另一个线程初始化,则将返回该值,而不执行初始化。

表5-1表明了三种模式的区别。

表5-1 三种模式的区别

最后,总结一下lateinit和by lazy的区别:

(1)lateinit只能用于修饰变量var,不能用于可空的属性和Java的基本类型。

(2)lateinit可以在任何位置初始化,并且可以初始化多次。

(3)lazy只能用于修饰常量val,并且lazy在SYNCHRONIZED、PUBLICATION模式下是线程安全的。

(4)lazy在第一次被调用时就被初始化,以后调用该属性会返回之前的结果。