咱们好,kotlin的特点托付、类托付、lazy等托付在日常的开发中,给咱们供给了很大的协助,我之前的文章也是有实战过几种托付。不过比照托付完成的背面机制一直都没有剖析过,所以本篇文章主要是带领咱们剖析下托付的完成原理,加深对kotlin的理解。

一. lazy托付

这儿咱们不说用法,直接说背面的完成原理。

先看一段代码:

val content: String by lazy {
    "oiuytrewq"
}
fun main() {
    println(content.length)
}

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

浅析一下:kotlin委托背后的实现机制
  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代码已然看不到,咱们退一步看下字节码

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

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

咱们看下DelegateDemoKt$content$2类的完成字节码:

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

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代码看下:

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

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

首要咱们看下FruitProxy这个类,其完成了Fruit接口,借助特点托付特性,编译器会自动协助咱们生成type() 接口办法的完成,并再其间调用结构办法传入的托付类目标modeltype()办法,类托付的中心逻辑就这些。

再main()办法中结构FruitProxy时,咱们也无法知晓详细的结构参数目标是啥,和上面的lazy一样,咱们看下字节码:

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

其实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优化小技巧,了解一下~