kotlin—内联类及其原理

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 (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. ()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. ()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. ()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—内联类及其原理)