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),它可以提示包裝類的性能。