美文网首页
Kotlin 类扩展实现原理

Kotlin 类扩展实现原理

作者: markRao | 来源:发表于2021-07-19 10:34 被阅读0次

    在 Kotlin 中当项目集成第三方 SDK 的时候,如果需要为其中某个类新增方法来可以通过 className.methodName(){}, 即 类名.方法名 的形式来扩展函数,那么同样和 Java 一样是 JVM 语言的 Kt 为什么就可以实现这种功能呢,以下为一个例子,借助它来详细探讨一下实现原理及细节。

    open class Father {
        //定义成员函数
        open fun shout() = println("Father call shout()")
    }
    class Son : Father() {
        //子类重写父类成员函数
        override fun shout() {
            println("Son call shout()")
        }
    }
    // 定义子类和父类扩展函数
    fun Father.eat() = println("Father call eat()")
    fun Son.eat() = println("Son call eat()")
    
    fun main() {
        val obj: Father = Son()
        obj.shout()
        obj.eat()
    }
    
    // 执行结果
    Son call shout()
    Father call eat()
    

    在 IDEA 中,打开 Kt 字节码预览如下

    public final class test/Son extends test/Father {
    // 省略 Son 字节码细节
    }
    
    public class test/Father {
    // 省略 Father 字节码细节
    }
    
    public final class test/Test16Kt {
    
      // Father 的类扩展实际实现
      public final static eat(Ltest/Father;)V
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
       L0
        ALOAD 0
        LDC "$this$eat"
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 16 L1
        LDC "Father call eat()"
        ASTORE 1
       L2
        ICONST_0
        ISTORE 2
       L3
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        ALOAD 1
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
       L4
       L5
        LINENUMBER 16 L5
        RETURN
       L6
        LOCALVARIABLE $this$eat Ltest/Father; L0 L6 0
        MAXSTACK = 2
        MAXLOCALS = 3
    
      // // Son 的类扩展实际实现
      public final static eat(Ltest/Son;)V
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
       L0
        ALOAD 0
        LDC "$this$eat"
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 18 L1
        LDC "Son call eat()"
        ASTORE 1
       L2
        ICONST_0
        ISTORE 2
       L3
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        ALOAD 1
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
       L4
       L5
        LINENUMBER 18 L5
        RETURN
       L6
        LOCALVARIABLE $this$eat Ltest/Son; L0 L6 0
        MAXSTACK = 2
        MAXLOCALS = 3
    
      // access flags 0x19
      public final static main()V
       L0
        LINENUMBER 21 L0
    // 实例化 Son
        NEW test/Son
        DUP
    // 调用 Son 类的 init 方法
        INVOKESPECIAL test/Son.<init> ()V
    // 检查转换为 Father 类型
        CHECKCAST test/Father
        ASTORE 0
       L1
        LINENUMBER 22 L1
        ALOAD 0
    // 调用 Father.shot() ,但是因为实例为 Son ,所以执行的还是 Son 重写的 shot()
        INVOKEVIRTUAL test/Father.shout ()V
       L2
        LINENUMBER 23 L2
        ALOAD 0
    <-- 问题 1 -->
        INVOKESTATIC test/Test16Kt.eat (Ltest/Father;)V
       L3
        LINENUMBER 24 L3
        RETURN
       L4
        LOCALVARIABLE obj Ltest/Father; L1 L4 0
        MAXSTACK = 2
        MAXLOCALS = 1
    
      // access flags 0x1009
      public static synthetic main([Ljava/lang/String;)V
        INVOKESTATIC test/Test16Kt.main ()V
        RETURN
        // 省略部分无关的实现
      // compiled from: test16.kt
    }
    ����
    �test�Test16Kt"*
    

    上述代码示例的 kt 文件名为 Test16,在问题 1 ,我们类中的代码 obj.eat() 在字节码中实际上是调用了 Test16Kt.eat(Ltest/Father;)V ,那么根据这个规律可以得知,类扩展实际上生成了一个当前文件名+Kt 的 class,然后把已扩展的实例作为参数传递进去,具体我们可以查看 Test16Kt 类中 public final static eat(Ltest/Son;)V 和 public final static eat(Ltest/Father;)V,那么最后一个疑问,为什么 obj 是 Son 的实例却调用了父类的扩展函数,子类调用父类扩展函数的原因,根据类扩展的字节码实现可以得知这不是因为继承,实际原因是在申明时把类型设置为 Father,如果将代码改为 val obj = Son(),那么字节码中就是调用 INVOKESTATIC test/Test16Kt.eat (Ltest/Son;)V

    相关文章

      网友评论

          本文标题:Kotlin 类扩展实现原理

          本文链接:https://www.haomeiwen.com/subject/zoscmltx.html