kotlin—內(nèi)聯(lián)類及其原理

1、什么是內(nèi)聯(lián)類?

內(nèi)聯(lián)類是一個(gè)對(duì)另一個(gè)類進(jìn)行包裝的類,既然是對(duì)其它類的包裝,那么它有什么特別之處,值得kotlin使用專門的語(yǔ)法來(lái)支持?使用上內(nèi)聯(lián)確實(shí)像是普通的包裝類一樣,但是在使用時(shí)所創(chuàng)建的內(nèi)聯(lián)類對(duì)象經(jīng)過(guò)編譯之后,在運(yùn)行時(shí)不會(huì)創(chuàng)建真正的內(nèi)聯(lián)類對(duì)象而是返回被包裝的類實(shí)例對(duì)象,在用到內(nèi)聯(lián)類對(duì)象的地方都會(huì)被編譯為使用內(nèi)聯(lián)類內(nèi)部的靜態(tài)方法。
對(duì)其它類進(jìn)行包裝時(shí),優(yōu)先考慮時(shí)候內(nèi)聯(lián)類,它可以避免創(chuàng)建包裝類對(duì)象,減少內(nèi)存提高性能。

2、語(yǔ)法

內(nèi)聯(lián)類的聲明語(yǔ)法只在class 前面加inline 進(jìn)行修飾, 并且提供一個(gè)主構(gòu)造函數(shù)參數(shù)是被包裝的類對(duì)象,語(yǔ)法如下所示:

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

內(nèi)聯(lián)類還是有不是限制的:

  • 主構(gòu)造器只能有一個(gè)參數(shù),且參數(shù)為被修飾的類對(duì)象
  • 不能繼承類,但可以繼承接口
  • 內(nèi)聯(lián)類不可被繼承
  • 不能有init語(yǔ)句塊
  • 不能有幕后字段,所以內(nèi)聯(lián)類只能有簡(jiǎn)單的計(jì)算屬性(不能包含延遲屬性/委托屬性)

使用內(nèi)聯(lián)類跟普通類一樣:

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

3、原理

使用內(nèi)聯(lián)類看起來(lái)與普通類一樣,只是聲明時(shí)多了個(gè)inline的修飾,怎么能體現(xiàn)出運(yùn)行時(shí)不會(huì)創(chuàng)建包裝類而是返回被包裝的類實(shí)例呢?
我們以一個(gè)案例及字節(jié)碼進(jìn)行分析:
使用內(nèi)聯(lián)類的源碼如下:

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類經(jīng)過(guò)編譯后的關(guān)鍵字節(jié)碼文件如下:

//內(nèi)聯(lián)類被final修飾,表示不能被繼承
public final class com/java/test/kt/TInline {

  // compiled from: TInline.kt
  //省略....
  // access flags 0x12
  //主構(gòu)造函數(shù)中的參數(shù)即被包裝的類String對(duì)象str
  private final Ljava/lang/String; str
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x11
  //自動(dòng)生成被包裝類對(duì)象的get方法,返回被包裝類對(duì)象本身
  public final getStr()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 3 L0
    ALOAD 0 //將第1個(gè)變量,即this載入棧頂
    GETFIELD com/java/test/kt/TInline.str : Ljava/lang/String; //獲取內(nèi)聯(lián)類的str字段,等價(jià)于this.str
    ARETURN //返回棧頂?shù)闹?   //省略....

  // access flags 0x1002
  //自動(dòng)生成init方法
  private synthetic <init>(Ljava/lang/String;)V
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1 //將第2個(gè)變量即方法的參數(shù)載入棧頂
    LDC "str" //常量池中的str的值
    //校驗(yàn)str對(duì)象不能為空
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
    ALOAD 0 //將第1個(gè)變量即this載入棧頂
    //調(diào)用父類的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轉(zhuǎn)為getA-impl靜態(tài)方法,返回的是a的值
  public final static getA-impl(Ljava/lang/String;)I
   L0
    LINENUMBER 5 L0
    //將a的get值123壓入棧頂
    BIPUSH 123
    IRETURN //返回棧頂?shù)闹导?23
  //省略....

  // access flags 0x19
  //將屬性length轉(zhuǎn)為getLength-impl靜態(tài)方法,返回的是length的get值
  public final static getLength-impl(Ljava/lang/String;)I
   L0
    LINENUMBER 7 L0
    ALOAD 0 //載入str到棧頂
    INVOKEVIRTUAL java/lang/String.length ()I //調(diào)用棧頂str對(duì)象的length方法,并把值壓入棧頂
    IRETURN  //返回棧頂str的length()值
   //省略....

  // access flags 0x9
  //虛擬構(gòu)造函數(shù)的靜態(tài)實(shí)現(xiàn)方法,參數(shù)為str對(duì)象
  public static constructor-impl(Ljava/lang/String;)Ljava/lang/String;
  //省略....
   L0
    ALOAD 0 //將第一個(gè)變量即方法的第1個(gè)參數(shù)str載入棧頂
    LDC "str" //從常量池中取str放入棧頂
    //校驗(yàn)棧頂對(duì)象即str不能為空
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
    ALOAD 0  //將第一個(gè)變量即方法的第1個(gè)參數(shù)str載入棧頂
    ARETURN //返回棧頂即str對(duì)象
   //省略....

  //省略....

  // access flags 0x9
  //內(nèi)聯(lián)對(duì)象轉(zhuǎn)為字符串的靜態(tài)方法
  public static toString-impl(Ljava/lang/String;)Ljava/lang/String;
  //省略....
    //新建StringBuilder對(duì)象
    NEW java/lang/StringBuilder
    DUP
    //調(diào)用StringBuilder的init方法
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    //StringBuilder對(duì)象添加字符串
    LDC "TInline(str="
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    //將第1個(gè)局部變量即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;  
    //調(diào)用StringBuilder的toString 方法,并把返回值放入棧頂
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN //返回棧頂?shù)闹?   //省略....

    //省略....
}

內(nèi)聯(lián)類TInline編譯之后可以看到:

  • 內(nèi)聯(lián)類內(nèi)部的屬性編譯之后變成靜態(tài)方法的getXXX-impl方法,返回的是屬性的值
  • 內(nèi)聯(lián)類內(nèi)部生成一個(gè)constructor-impl靜態(tài)方法,返回的是被包裝類即str本身

我們接著看看使用內(nèi)聯(lián)類的TInlineClient編譯之后發(fā)生了什么:

// 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"載入棧頂
    //調(diào)用TInline的constructor-impl方法,參數(shù)為棧頂abc的字符串,并把返回值放入棧頂
    INVOKESTATIC com/java/test/kt/TInline.constructor-impl (Ljava/lang/String;)Ljava/lang/String;
    //把棧頂?shù)闹蒂x值給第2個(gè)局部變量obj
    ASTORE 1
   L1
    LINENUMBER 13 L1
    //創(chuàng)建StringBuilder對(duì)象
    NEW java/lang/StringBuilder
    DUP
    //調(diào)用StringBuilder的init方法
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "a = "  //將常量池中的"a = "載入棧頂
    //調(diào)用StringBuilder的append方法拼接棧頂"a = "字符串
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1 //載入第2個(gè)局部變量即obj到棧頂
    //調(diào)用TInline.getA-impl 方法,棧頂即obj作為參數(shù),并將結(jié)果放入棧頂
    INVOKESTATIC com/java/test/kt/TInline.getA-impl (Ljava/lang/String;)I
    //調(diào)用StringBuilder的append方法拼接棧頂?shù)闹?    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    LDC ", length = " //將常量池中的", length = "載入棧頂
     //調(diào)用StringBuilder的append方法拼接棧頂?shù)闹?    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1  //載入第2個(gè)局部變量即obj到棧頂
    //調(diào)用TInline.getLength-impl 方法,棧頂即obj作為參數(shù),并將結(jié)果放入棧頂
    INVOKESTATIC com/java/test/kt/TInline.getLength-impl (Ljava/lang/String;)I
    //調(diào)用StringBuilder的append方法拼接棧頂?shù)闹?    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    LDC ", val = " //將常量池中的", val = " 載入棧頂
    //調(diào)用StringBuilder的append方法拼接棧頂?shù)闹?    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1  //載入第2個(gè)局部變量即obj到棧頂
    //調(diào)用StringBuilder的append方法拼接棧頂obj的值,obj本身是String
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
     //調(diào)用StringBuilder的toString 方法,并將返回值放入棧頂
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2  //將棧頂?shù)闹蒂x值給第3個(gè)局部變量即sVal
   //省略....
}

TInlineClient編譯之后,使用內(nèi)聯(lián)類TInline的地方都轉(zhuǎn)為調(diào)用TInline的靜態(tài)方法,創(chuàng)建TInline對(duì)象的地方變成了調(diào)用TInline的constructor-impl靜態(tài)方法,參數(shù)是被包裝的類,TInline的constructor-impl返回的是其實(shí)就是被包裝類本身,使用TInline的屬性/方法的地方都變?yōu)檎{(diào)用其內(nèi)部的靜態(tài)方法。

總結(jié):
內(nèi)聯(lián)類在編譯之后,會(huì)自動(dòng)生成屬性/方法/被包裝類的靜態(tài)方法,在使用時(shí)都是轉(zhuǎn)為調(diào)用對(duì)應(yīng)的靜態(tài)方法,創(chuàng)建內(nèi)聯(lián)類的地方也會(huì)變成調(diào)用constructor-impl靜態(tài)方法,返回的是被包裝類本身。所以內(nèi)聯(lián)類提升包裝類的性能,因?yàn)樗粫?huì)創(chuàng)建真實(shí)的內(nèi)聯(lián)類對(duì)象,而是返回被包裝類本身。

在使用包裝類的場(chǎng)景可以考慮使用內(nèi)聯(lián)類實(shí)現(xiàn),它可以提示包裝類的性能。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容