注册

浅析一下:kotlin委托背后的实现机制

大家好,kotlin的属性委托、类委托、lazy等委托在日常的开发中,给我们提供了很大的帮助,我之前的文章也是有实战过几种委托。不过对比委托实现的背后机制一直都没有分析过,所以本篇文章主要是带领大家分析下委托的实现原理,加深对kotlin的理解。


一. lazy委托


这里我们不说用法,直接说背后的实现原理。


先看一段代码:


val content: String by lazy {
"oiuytrewq"
}

fun main() {
println(content.length)
}

我们看下反编译后的java代码:




  1. 首先会通过DelegateDemoKt静态代码块饿汉式的方式创建一个Lazy类型的变量content$delegate,命名的规则即代码中定义的原始变量值拼接上$delegate,我们原始定义的content变量就会从属性定义上消失,但会生成对应的get方法,即getContent()


  1. 当我们在main方法中调用content.length时,其实就是调用getContent().length(),而getContent()最终是调用了content$delegate.getValue方法;


  1. 这个lazy类型的变量是调用了LazyKt.lazy()方法创建,而真正的核心逻辑——该方法具体参数的传入,在反编译的java代码中并没有体现;

java代码既然看不到,我们退一步看下字节码:



上面是DelegateDemoKt类构造器对应的字节码,其中就是获取了DelegateDemoKt$content$2作为参数传入了LazyKt.lazy()方法。


我们看下DelegateDemoKt$content$2类的实现字节码:



DelegateDemoKt$content$2类实现了Function0接口,所以上面lazy的真正实现逻辑就是DelegateDemoKt$content$2类的invoke方法中,上图的字节码红框圈出的地方就很直观的看出来了。


二. 属性委托


属性委托的委托类就是指实现了ReadWritePropertyReadOnlyProperty接口的类,像官方提供的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);
}
}


  1. 和lazy有些类似,会生成一个实现了ReadWriteProperty接口的匿名类变量age$delegate,命名规则和lazy相同,通过还帮助我们生成了对应的getAgesetAge方法;


  1. 当我们在代码中执行age = 4就会调用setAge(4)方法,最终会调用age$delegate.setValue()方法;类似的调用age就会调用getAge(),最终调用到age$delegate.getValue()方法;


  1. 编译器还通过反射帮助我们生成了一个KProperty类型的$$delegatedProperties变量,主要是ReadWritePropertysetValuegetValue方法都需要传入这样一个类型的对象,通过$$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();
}
}


  1. 生成一个Map类型的name$delegate变量,这个变量其实就是我们定义的map散列表;


  1. 通过反射生成了一个KProperty类型对象变量$$delegatedProperties,通过这个对象的getName()我们就能拿到变量名称,比如这里的"name"变量名;


  1. 最终调用了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() 接口方法的实现,并再其中调用构造方法传入的委托类对象modeltype()方法,类委托的核心逻辑就这些。


再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优化小技巧

,了解一下~

0 个评论

要回复文章请先登录注册