美文网首页
kotlin—内联类及其原理

kotlin—内联类及其原理

作者: jxiang112 | 来源:发表于2022-05-18 11:15 被阅读0次

    1、什么是内联类?

    内联类是一个对另一个类进行包装的类,既然是对其它类的包装,那么它有什么特别之处,值得kotlin使用专门的语法来支持?使用上内联确实像是普通的包装类一样,但是在使用时所创建的内联类对象经过编译之后,在运行时不会创建真正的内联类对象而是返回被包装的类实例对象,在用到内联类对象的地方都会被编译为使用内联类内部的静态方法。
    对其它类进行包装时,优先考虑时候内联类,它可以避免创建包装类对象,减少内存提高性能。

    2、语法

    内联类的声明语法只在class 前面加inline 进行修饰, 并且提供一个主构造函数参数是被包装的类对象,语法如下所示:

    inline class InlineClassName(val otherClass: ClassType) [:superInterfaceList]{
      [override list]
      [params/funs]
    }
    

    内联类还是有不是限制的:

    • 主构造器只能有一个参数,且参数为被修饰的类对象
    • 不能继承类,但可以继承接口
    • 内联类不可被继承
    • 不能有init语句块
    • 不能有幕后字段,所以内联类只能有简单的计算属性(不能包含延迟属性/委托属性)

    使用内联类跟普通类一样:

    val inlineObj = InlineClass(otherClassObj)
    inlineObj.param
    inlineObj.fun(xxx)
    

    3、原理

    使用内联类看起来与普通类一样,只是声明时多了个inline的修饰,怎么能体现出运行时不会创建包装类而是返回被包装的类实例呢?
    我们以一个案例及字节码进行分析:
    使用内联类的源码如下:

    inline class TInline(val str: String) {
        val a
            get() = 123
        val length: Int
            get() = str.length
    }
    
    class TInlineClient() {
        fun main() {
            val obj = TInline("abc")
            val sVal = "a = ${obj.a}, length = ${obj.length}, val = ${obj.str}"
            println("sVal = $obj")
        }
    }
    

    TInline类经过编译后的关键字节码文件如下:

    //内联类被final修饰,表示不能被继承
    public final class com/java/test/kt/TInline {
    
      // compiled from: TInline.kt
      //省略....
      // access flags 0x12
      //主构造函数中的参数即被包装的类String对象str
      private final Ljava/lang/String; str
      @Lorg/jetbrains/annotations/NotNull;() // invisible
    
      // access flags 0x11
      //自动生成被包装类对象的get方法,返回被包装类对象本身
      public final getStr()Ljava/lang/String;
      @Lorg/jetbrains/annotations/NotNull;() // invisible
       L0
        LINENUMBER 3 L0
        ALOAD 0 //将第1个变量,即this载入栈顶
        GETFIELD com/java/test/kt/TInline.str : Ljava/lang/String; //获取内联类的str字段,等价于this.str
        ARETURN //返回栈顶的值
       //省略....
    
      // access flags 0x1002
      //自动生成init方法
      private synthetic <init>(Ljava/lang/String;)V
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
       L0
        ALOAD 1 //将第2个变量即方法的参数载入栈顶
        LDC "str" //常量池中的str的值
        //校验str对象不能为空
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 3 L1
        ALOAD 0 //将第1个变量即this载入栈顶
        //调用父类的init方法,即Object的init方法
        INVOKESPECIAL java/lang/Object.<init> ()V
        //给str赋值为常量池中str的值
        ALOAD 0
        ALOAD 1
        PUTFIELD com/java/test/kt/TInline.str : Ljava/lang/String;
        RETURN
       //省略....
    
      // access flags 0x19
      //将属性a转为getA-impl静态方法,返回的是a的值
      public final static getA-impl(Ljava/lang/String;)I
       L0
        LINENUMBER 5 L0
        //将a的get值123压入栈顶
        BIPUSH 123
        IRETURN //返回栈顶的值即123
      //省略....
    
      // access flags 0x19
      //将属性length转为getLength-impl静态方法,返回的是length的get值
      public final static getLength-impl(Ljava/lang/String;)I
       L0
        LINENUMBER 7 L0
        ALOAD 0 //载入str到栈顶
        INVOKEVIRTUAL java/lang/String.length ()I //调用栈顶str对象的length方法,并把值压入栈顶
        IRETURN  //返回栈顶str的length()值
       //省略....
    
      // access flags 0x9
      //虚拟构造函数的静态实现方法,参数为str对象
      public static constructor-impl(Ljava/lang/String;)Ljava/lang/String;
      //省略....
       L0
        ALOAD 0 //将第一个变量即方法的第1个参数str载入栈顶
        LDC "str" //从常量池中取str放入栈顶
        //校验栈顶对象即str不能为空
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 3 L1
        ALOAD 0  //将第一个变量即方法的第1个参数str载入栈顶
        ARETURN //返回栈顶即str对象
       //省略....
    
      //省略....
    
      // access flags 0x9
      //内联对象转为字符串的静态方法
      public static toString-impl(Ljava/lang/String;)Ljava/lang/String;
      //省略....
        //新建StringBuilder对象
        NEW java/lang/StringBuilder
        DUP
        //调用StringBuilder的init方法
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        //StringBuilder对象添加字符串
        LDC "TInline(str="
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        //将第1个局部变量即str载入栈顶
        ALOAD 0
       //StringBuilder添加栈顶str的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        //从常量池中载入")"
        LDC ")"
        //StringBuilder添加栈顶")"的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;  
        //调用StringBuilder的toString 方法,并把返回值放入栈顶
        INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        ARETURN //返回栈顶的值
       //省略....
    
        //省略....
    }
    

    内联类TInline编译之后可以看到:

    • 内联类内部的属性编译之后变成静态方法的getXXX-impl方法,返回的是属性的值
    • 内联类内部生成一个constructor-impl静态方法,返回的是被包装类即str本身

    我们接着看看使用内联类的TInlineClient编译之后发生了什么:

    // class version 52.0 (52)
    // access flags 0x31
    public final class com/java/test/kt/TInlineClient {
    
      // compiled from: TInline.kt
      //省略....
    
      // access flags 0x11
      public final main()V
       L0
        LINENUMBER 12 L0
        LDC "abc" //将常量池中的"abc"载入栈顶
        //调用TInline的constructor-impl方法,参数为栈顶abc的字符串,并把返回值放入栈顶
        INVOKESTATIC com/java/test/kt/TInline.constructor-impl (Ljava/lang/String;)Ljava/lang/String;
        //把栈顶的值赋值给第2个局部变量obj
        ASTORE 1
       L1
        LINENUMBER 13 L1
        //创建StringBuilder对象
        NEW java/lang/StringBuilder
        DUP
        //调用StringBuilder的init方法
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        LDC "a = "  //将常量池中的"a = "载入栈顶
        //调用StringBuilder的append方法拼接栈顶"a = "字符串
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        ALOAD 1 //载入第2个局部变量即obj到栈顶
        //调用TInline.getA-impl 方法,栈顶即obj作为参数,并将结果放入栈顶
        INVOKESTATIC com/java/test/kt/TInline.getA-impl (Ljava/lang/String;)I
        //调用StringBuilder的append方法拼接栈顶的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
        LDC ", length = " //将常量池中的", length = "载入栈顶
         //调用StringBuilder的append方法拼接栈顶的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        ALOAD 1  //载入第2个局部变量即obj到栈顶
        //调用TInline.getLength-impl 方法,栈顶即obj作为参数,并将结果放入栈顶
        INVOKESTATIC com/java/test/kt/TInline.getLength-impl (Ljava/lang/String;)I
        //调用StringBuilder的append方法拼接栈顶的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
        LDC ", val = " //将常量池中的", val = " 载入栈顶
        //调用StringBuilder的append方法拼接栈顶的值
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        ALOAD 1  //载入第2个局部变量即obj到栈顶
        //调用StringBuilder的append方法拼接栈顶obj的值,obj本身是String
        INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
         //调用StringBuilder的toString 方法,并将返回值放入栈顶
        INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        ASTORE 2  //将栈顶的值赋值给第3个局部变量即sVal
       //省略....
    }
    

    TInlineClient编译之后,使用内联类TInline的地方都转为调用TInline的静态方法,创建TInline对象的地方变成了调用TInline的constructor-impl静态方法,参数是被包装的类,TInline的constructor-impl返回的是其实就是被包装类本身,使用TInline的属性/方法的地方都变为调用其内部的静态方法。

    总结:
    内联类在编译之后,会自动生成属性/方法/被包装类的静态方法,在使用时都是转为调用对应的静态方法,创建内联类的地方也会变成调用constructor-impl静态方法,返回的是被包装类本身。所以内联类提升包装类的性能,因为它不会创建真实的内联类对象,而是返回被包装类本身。

    在使用包装类的场景可以考虑使用内联类实现,它可以提示包装类的性能。

    相关文章

      网友评论

          本文标题:kotlin—内联类及其原理

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