咱们好,kotlin的特点托付、类托付、lazy等托付在日常的开发中,给咱们供给了很大的协助,我之前的文章也是有实战过几种托付。不过比照托付完成的背面机制一直都没有剖析过,所以本篇文章主要是带领咱们剖析下托付的完成原理,加深对kotlin的理解。
一. lazy
托付
这儿咱们不说用法,直接说背面的完成原理。
先看一段代码:
val content: String by lazy {
"oiuytrewq"
}
fun main() {
println(content.length)
}
咱们看下反编译后的java代码:
- 首要会经过
DelegateDemoKt
静态代码块饿汉式的方式创立一个Lazy
类型的变量content$delegate
,命名的规矩即代码中界说的原始变量值拼接上$delegate
,咱们原始界说的content变量就会从特点界说上消失,但会生成对应的get办法,即getContent()
;
- 当咱们在
main
办法中调用content.length
时,其实便是调用getContent().length()
,而getContent()
终究是调用了content$delegate.getValue
办法;
- 这个lazy类型的变量是调用了
LazyKt.lazy()
办法创立,而真实的中心逻辑——该办法详细参数的传入,在反编译的java代码中并没有表现;
java代码已然看不到,咱们退一步看下字节码:
上面是DelegateDemoKt
类结构器对应的字节码,其间便是获取了DelegateDemoKt$content$2
作为参数传入了LazyKt.lazy()
办法。
咱们看下DelegateDemoKt$content$2
类的完成字节码:
DelegateDemoKt$content$2
类完成了Function0
接口,所以上面lazy
的真实完成逻辑便是DelegateDemoKt$content$2
类的invoke
办法中,上图的字节码红框圈出的当地就很直观的看出来了。
二. 特点托付
特点托付的托付类便是指完成了ReadWriteProperty
和ReadOnlyProperty
接口的类,像官方供给的Delegates.observable()
和Delegates.vetoable()
这两个api也是借助前面两个接口完成的。这儿咱们就以支持读写的ReadWriteProperty
托付接口进行举例剖析。
先看一段比如代码:
var age: Int by object : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return 10
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
val v = value * value
println("setValue: $v")
}
}
fun main() {
age = 4
println(age)
}
咱们看下反编译的java代码:
public final class DelegateDemoKt {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty0(new MutablePropertyReference0Impl(DelegateDemoKt.class, "age", "getAge()I", 1))};
@NotNull
private static final <undefinedtype> age$delegate = new ReadWriteProperty() {
@NotNull
public Integer getValue(@Nullable Object thisRef, @NotNull KProperty property) {
Intrinsics.checkNotNullParameter(property, "property");
return 10;
}
public void setValue(@Nullable Object thisRef, @NotNull KProperty property, int value) {
Intrinsics.checkNotNullParameter(property, "property");
int v = value * value;
String var5 = "setValue: " + v;
System.out.println(var5);
}
};
public static final int getAge() {
return age$delegate.getValue((Object)null, $$delegatedProperties[0]);
}
public static final void setAge(int var0) {
age$delegate.setValue((Object)null, $$delegatedProperties[0], var0);
}
public static final void main() {
setAge(4);
int var0 = getAge();
System.out.println(var0);
}
}
- 和lazy有些类似,会生成一个完成了
ReadWriteProperty
接口的匿名类变量age$delegate
,命名规矩和lazy相同,经过还协助咱们生成了对应的getAge
和setAge
办法;
- 当咱们在代码中履行
age = 4
就会调用setAge(4)
办法,终究会调用age$delegate.setValue()
办法;类似的调用age
就会调用getAge()
,终究调用到age$delegate.getValue()
办法;
- 编译器还经过反射协助咱们生成了一个
KProperty
类型的$$delegatedProperties
变量,主要是ReadWriteProperty
的setValue
和getValue
办法都需要传入这样一个类型的目标,经过$$delegatedProperties
变量咱们能够拜访到详细的变量名等信息;
类似的还有一种特点托付,咱们看下代码:
val map = mutableMapOf<String, Int>()
val name: Int by map
上面代码的意思是:当拜访name时,就会从map这个散列表中获取key为”name”的value值并回来,不存在就直接抛异常,接下来咱们看下反编译后的java代码:
public final class DelegateDemoKt {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.property0(new PropertyReference0Impl(DelegateDemoKt.class, "name", "getName()I", 1))};
@NotNull
private static final Map map = (Map)(new LinkedHashMap());
@NotNull
private static final Map name$delegate;
static {
name$delegate = map;
}
public static final int getName() {
Map var0 = name$delegate;
Object var1 = null;
KProperty var2 = $$delegatedProperties[0];
return ((Number)MapsKt.getOrImplicitDefaultNullable(var0, var2.getName())).intValue();
}
}
- 生成一个Map类型的
name$delegate
变量,这个变量其实便是咱们界说的map
散列表;
- 经过反射生成了一个
KProperty
类型目标变量$$delegatedProperties
,经过这个目标的getName()
咱们就能拿到变量名称,比如这儿的”name”变量名;
- 终究调用了
MapsKt.getOrImplicitDefaultNullable
办法,去map
散列表去查找”name”这个key对应的value;
PS:记住kotlin1.6仍是1.7的插件版别对应托付进行了优化,这个后续的文章会再进行讲解。
三. 类托付
类托付完成就比较简单了,这儿咱们看下样例代码:
fun interface Fruit {
fun type(): Int
}
class FruitProxy(private val model: Fruit) : Fruit by model
fun main() {
val proxy: FruitProxy = FruitProxy {
-1
}
println(proxy.type())
}
反编译成java代码看下:
首要咱们看下FruitProxy
这个类,其完成了Fruit
接口,借助特点托付特性,编译器会自动协助咱们生成type()
接口办法的完成,并再其间调用结构办法传入的托付类目标model
的type()
办法,类托付的中心逻辑就这些。
再main()办法中结构FruitProxy
时,咱们也无法知晓详细的结构参数目标是啥,和上面的lazy一样,咱们看下字节码:
其实FruitProxy
办法就传入了一个DelegateDemoKt$main$proxy$1
类型的目标,并完成了Fruit
接口重写了type
办法。
总结
本篇文章主要是讲解了三种托付背面的完成原理,有时候反编译字节码看不出来原理的,能够从字节码中寻觅答案,希望本篇文章能对你有所协助。
历史文章
这儿是我整理的过往kotlin特性介绍的历史文章,咱们感兴趣能够阅读下:
Kotlin1.9.0-Beta,它来了!!
聊聊Kotlin1.7.0版别供给的一些特性
聊聊kotlin1.5和1.6版别供给的一些新特性
kotlin密封sealed class/interface的迭代之旅
优化@BuilderInference注解,Kotlin高版别下了这些“毒手”!
@JvmDefaultWithCompatibility优化小技巧,了解一下~