Koltin中属性在声明的同时也要求要被初始化,不然会报错。 例如如下代码:java
private var name0: String //报错 private var name1: String = "xiaoming" //不报错 private var name2: String? = null //不报错 复制代码
但是有的时候,我并不想声明一个类型可空的对象,并且我也没办法在对象一声明的时候就为它初始化,那么这时就须要用到Kotlin提供的延迟初始化。
Kotlin中有两种延迟初始化的方式。一种是lateinit var,一种是by lazy。安全
private lateinit var name: String 复制代码
lateinit var只能用来修饰类属性,不能用来修饰局部变量,而且只能用来修饰对象,不能用来修饰基本类型(由于基本类型的属性在类加载后的准备阶段都会被初始化为默认值)。
lateinit var的做用也比较简单,就是让编译期在检查时不要由于属性变量未被初始化而报错。
Kotlin相信当开发者显式使用lateinit var 关键字的时候,他必定也会在后面某个合理的时机将该属性对象初始化的(然而,谁知道呢,也许他用完才想起还没初始化)。bash
by lazy自己是一种属性委托。属性委托的关键字是by
。by lazy 的写法以下:markdown
//用于属性延迟初始化 val name: Int by lazy { 1 } //用于局部变量延迟初始化 public fun foo() { val bar by lazy { "hello" } println(bar) } 复制代码
如下以name属性为表明来说解by kazy的原理,局部变量的初始化也是同样的原理。
by lazy要求属性声明为val
,即不可变变量,在java中至关于被final
修饰。
这意味着该变量一旦初始化后就不容许再被修改值了(基本类型是值不能被修改,对象类型是引用不能被修改)。{}
内的操做就是返回惟一一次初始化的结果。
by lazy可使用于类属性或者局部变量。jvm
写一段最简单的代码分析by lazy的实现:编辑器
class TestCase { private val name: Int by lazy { 1 } fun printname() { println(name) } } 复制代码
在IDEA中点击toolbar中的 Tools -> Kotlin -> Show Kotlin ByteCode, 查看编辑器右侧的工具栏:
ide
更完整的字节码片断以下:函数
public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V L1 LINENUMBER 5 L1 ALOAD 0 GETSTATIC com/rhythm7/bylazy/TestCase$name$2.INSTANCE : Lcom/rhythm7/bylazy/TestCase$name$2; CHECKCAST kotlin/jvm/functions/Function0 INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy; PUTname com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy; RETURN L2 LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1 复制代码
该段代码是在字节码生成的public <clinit>()V
方法内的。之因此是在该方法内,是由于非单例object的Kotlin类的属性初始化代码语句通过编译器处理后都会被收集到该方法内,若是是object对象,对应的属性初始化代码语句则会被收集到static <clinit>()V
方法中。另外,在字节码中,这两个方法是拥有不一样方法签名的,这与语言级别上判断两个方法是否相同的方式有所不一样。前者是实例构造方法,后者是类构造方法。
L0与L1之间的字节码表明调用了Object()的构造方法,这是默认的父类构造方法。L2以后的是本地变量表说明。L1与L2之间的字节码对应以下kotlin代码:工具
private val name: Int by lazy { 1 } 复制代码
L1与L2之间这段字节码的意思是:
源代码行号5对应字节码方法体内的行号1; 将this(非静态方法默认的第一个本地变量)推送至栈顶;
获取静态变量com.rhythm7.bylazy.TestCase$name$2.INSTANCE
;
检验INSTANCE可否转换为kotlin.jvm.functions.Function0
类;
调用静态方法kotlin.LazyKt.lazy(kotlin.jvm.functions.Function0)
,将INSTANCE做为参数传入,并得到一个kotlin.Lazy
类型的返回值;
将以上返回值赋值给com.rhythm7.bylazy.TestCase.name$delegate
;
最后结束方法。ui
至关于java代码:
TestCase() { name$delegate = LazyKt.lazy((Function0)name$2.INSTANCE) } 复制代码
其中name$delegate
是编译后生成的属性,对象类型为Lazy。
private final Lkotlin/Lazy; name$delegate 复制代码
name$2
都是编译后生成的内部类。
final class com/rhythm7/bylazy/TestCase$name$2 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 复制代码
name$2
继承了kotlin.jvm.internal.Lambda类并实现了kotlin.jvm.functions.Function0接口, 能够看出name$2
其实就是kotlin函数参数类型()->T
的具体实现,经过字节码分析不难知道name$2.INSTANCE则是该实现类的一个静态对象实例。
因此以上字节码又至关于Koltin中的:
init {
name$delegate = lazy(()->{})
}
复制代码
然而,这些代码的做用仅仅是给一个编译期生成的属性变量赋值而已,并无其余的操做。
真正实现属性变量延迟初始化的地方实际上是在属性name的getter方法里。
若是在java代码中调用过kotlin代码,会发现java代码中只能经过setter或getter的方式访问koltin编写的对象属性,这是由于kotlin中默认会对属性添加private
修饰符,并根据该属性变量是val
仍是var
生成getter或getter和setter一块儿生成。而后又根据对该属性的访问权限给getter和setter添加对应的访问权限修饰符(默认是public)。
查看getName()的具体实现:
private final getName()I L0 ALOAD 0 GETFIELD com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy; ASTORE 1 ALOAD 0 ASTORE 2 GETSTATIC com/rhythm7/bylazy/TestCase.$$delegatedProperties : [Lkotlin/reflect/KProperty; ICONST_0 AALOAD ASTORE 3 L1 ALOAD 1 INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; L2 CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/Number.intValue ()I IRETURN L3 LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L3 0 MAXSTACK = 2 MAXLOCALS = 4 复制代码
至关于java代码:
private final int getName(){ Lazy var1 = this.name$delegate; KProperty var2 = this.?delegatedProperties[0] return ((Number)var1.getValue()).intValue() } 复制代码
能够看到name的getter方法实际上是返回了 name$delegate.getValue()
方法。?delegatedProperties
是编译后自动生成的属性,但在此处并无用到,因此不用关心。
那么如今咱们要关心的就只有name$delegate.getValue()
,也就是Lazy类getValue()
方法的具体实现了。
先看LazyKt.lazy(()->T)的实现:
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) 复制代码
再看SynchronizedLazyImpl
类的具体实现:
private object UNINITIALIZED_VALUE private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable { private var initializer: (() -> T)? = initializer @Volatile private var _value: Any? = UNINITIALIZED_VALUE // final field is required to enable safe publication of constructed instance private val lock = lock ?: this override val value: T get() { val _v1 = _value if (_v1 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return _v1 as T } return synchronized(lock) { val _v2 = _value if (_v2 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") (_v2 as T) } else { val typedValue = initializer!!() _value = typedValue initializer = null typedValue } } } ...... } 复制代码
以上代码的阅读难度就很是低了。
SynchronizedLazyImpl继承了Lazy类,并指定了泛型类型,而后重写了Lazy父类的getValue()方法。 getValue()方法中会对_value
是否已初始化作判断,并返回_value
,从而实现value的延迟初始化的做用。
注意,对value的初始化行为自己是线程安全的。
总结一下,当一个属性name须要by lazy时,具体是怎么实现的:
那么,再总结一下,lateinit var和by lazy哪一个更好用? 首先二者的应用场景是略有不一样的。 而后,虽然二者均可以推迟属性初始化的时间,可是lateinit var只是让编译期忽略对属性未初始化的检查,后续在哪里以及什么时候初始化还须要开发者本身决定。 而by lazy真正作到了声明的同时也指定了延迟初始化时的行为,在属性被第一次被使用的时候能自动初始化。但这些功能是要为此付出一丢丢代价的。