背景
kotlin中的语法糖by lazy相信都有用过,但是这里面的秘密却很少有人深究下去,还有网上充斥着大量的文章,却很少能说到本质的点上,所以本jetbrains是什么软件文以字节码的视角,揭开by lazy的秘密。
一个例子
class LazyClassTest {
val lazyTest :Test by lazy {
Log.i("hello","初始化") 1
Test()
}
fun test(){
Log.i("hello","$lazyTest")
Log.i("hello","$lazyTest")
}
}
如果执行test方法,请问代号为1的log会输出几次呢?答案是1次,明明我们在test方法中执行了两次lazyTest的获取,这其中有什么不为人知的事情吗!?其实这是kotlin在编译的时候给我们施加了魔法。
编译器背后的事情
为了看清楚编译器的事情,我们直接查看编译后的字节码,这里贴出来,后面解释
删除不必要的信息
// access flags 0x18
final static INNERCLASS com/example/newtestproject/LazyClassTest$lazyTest$2 null null
// access flags 0x12
private final Lkotlin/Lazy; lazyTest$delegate
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x1
public <init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 7 L1
ALOAD 0
GETSTATIC com/example/newtestproject/LazyClassTest$lazyTest$2.INSTANCE : Lcom/example/newtestproject/LazyClassTest$lazyTest$2;
CHECKCAST kotlin/jvm/functions/Function0
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
PUTFIELD com/example/newtestproject/LazyClassTest.lazyTest$delegate : Lkotlin/Lazy;
L2
LINENUMBER 5 L2
RETURN
L3
LOCALVARIABLE this Lcom/example/newtestproject/LazyClassTest; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x11
public final getLazyTest()Lcom/example/newtestproject/Test;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 7 L0
ALOAD 0
GETFIELD com/example/newtestproject/LazyClassTest.lazyTest$delegate : Lkotlin/Lazy;
ASTORE 1
ALOAD 1
INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf)
CHECKCAST com/example/newtestproject/Test
L1
LINENUMBER 7 L1
ARETURN
L2
LOCALVARIABLE this Lcom/example/newtestproject/LazyClassTest; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 2
// access flags 0x11
public final test()V
L0
LINENUMBER 13 L0
LDC "hello"
ALOAD 0
INVOKEVIRTUAL com/example/newtestproject/LazyClassTest.getLazyTest ()Lcom/example/newtestproject/Test;
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
POP
L1
LINENUMBER 14 L1
LDC "hello"
ALOAD 0
INVOKEVIRTUAL com/example/newtestproject/LazyClassTest.getLazyTest ()Lcom/example/newtestproject/Test;
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
POP
L2
LINENUMBER 15 L2
RETURN
L3
LOCALVARIABLE this Lcom/example/newtestproject/LazyClassTest; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
我们初始化磁盘惊讶的发现,原本的类中居然多出了一个内部类com/example/newtestprojeandroid/harmonyosct/LazyClassTestlazyTestlazyTest2,命名这么长!没错,它就是编译的时候生成的“魔法的种子”,那么这里内部类有什么特别的android下载安装地方吗?字节码层java怎么读面是看不出来初始化电脑的,因为这个这只是编初始化电脑译时期的内容,我们在虚拟机运行的时候来看,它初始化电脑时出现问题未进行更改其实是一个实现了一个接口是Lazy的内部类
public interface Lazy<out T> {
public abstract val value: T
public abstract fun isInitialized(): kotlin.Boolean
}
lazy背后的延时加载
为什么用了lazy就有懒加载的效果呢?其实关键就是这个,我们在iandroid是什么手机牌子ni初始化游戏启动器失败t阶段可以看到
getstatic 'com/example/newtestproject/LazyClassTest$lazyTest$2.INSTANCE','Lcom/example/newtestproject/LazyClassTest$lazyTest$2;'
checkcast 'kotlin/jvm/functions/Function0'
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
putfield 'com/example/newtestproject/LazyClassTest.lazyTest$delegate','Lkotlin/Lazy;'
在初始化的时候,只是调用了kotlin/LazyKt.lazy类的一个静态方法,针对属性复制的putfield指令,也只是对LazyClasjava是什么意思sTest.lazyTest$delegatejava培训这个内部类的一Android个Lkotlin/Lazy对象进行赋值,看起来其实跟我们的lazyTest变量毫无关系。真相是lazyTest具体的赋值操作被隐藏了而已。从这里就可以看到,为什么android的drawable类lazy是如何实现延时加载的!本质就是在初始化的时候只是生成一个内部类,不进行任何对目标对象进行赋值操作罢了!
获取操作
我android平板电脑价格们再观察一下对于lazyTest变量的访问操作,从字节码看到,每次对变量的获初始化sdk什么意思取都调用了LazyClassTest的getLazyTest方法android是什么手机牌子!这个也是编译器生成的方法,具体可以看到
public final com.example.newtestproject.Test getLazyTest() {
aload 0
getfield 'com/example/newtestproject/LazyClassTest.lazyTest$delegate','Lkotlin/Lazy;'
astore 1
aload 1
INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf)
checkcast 'com/example/newtestproject/Test'
areturn
}
天呐!我们越来越接近终点了,首先是通android什么意思过getfield指令获取了一个Lkotlin/Lazy变量,这个不就是上面我们赋值的初始化游戏启动器失败东西吗!然后调用了一个普通的方法getVa字节码是什么意思lue就结束java模拟器了,也就是说,每次对lazyTest变量的访问,都间接转发到了一个编译时生成的内部类中的一个特殊属性所调java培训用的字节码文件方法!看到这个,读者可能会思考,既然每次访问都是调用同一个方法,为什么我们by lazy时声明的lambad会只执行一次呢?编译时的字节码已经不能给我们带来答案了,这个因为像java虚拟机这种,关于具体类的调用会在运行时确定这个特性所带来的(区别于c/cpp)。
运行时的魔法
那好,我们还有最后一个神奇,就是debug,我们最终会发现初始化磁盘,在运行时by lazy的调用,其实最终都会转到如下代码的执行
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
}
}
}
这个类位于LazyJVM中(kotlin1.5.10),我们就找到最终的秘密了,原来一开始的时候变量就是UNINITIAjava培训LIZED_VALUE,经过一次赋值操作后,就会变成实际的T所指代的类型,下次再访问的时候,就直接满足if条件返回了!所以这就是一初始化sdk什么意思次赋值的秘密!还有我们可以看到,默认的by lazy操作第一次赋值时,是采用了syncJavahronized进行了加锁操作!
总结
我们已经全方位揭秘了by lazy的魔法面android是什么手机牌子纱Java,相信也初始化电脑对这个语法糖有了自己更深的理解,之所以写这篇字节码是什么意思文,是因为好多网上资料要么是含糊不清要么是无法解释本质,这里作为一个记录分享
往期更精彩:
想要实现JetBrains一个hook库吗,快看过来/post/710008…