本文收錄于 kotlin入門潛修專題系列什燕,歡迎學(xué)習(xí)交流。
創(chuàng)作不易竞端,如有轉(zhuǎn)載屎即,還請(qǐng)備注。
內(nèi)聯(lián)方法
在學(xué)習(xí)c/c++語言的時(shí)候事富,會(huì)了解到inline(內(nèi)聯(lián))方法技俐。java中并沒有inline方法,而kotlin提供了該功能统台,這是有別于java的一個(gè)地方雕擂。kotlin中使用inline關(guān)鍵字來修飾內(nèi)聯(lián)方法。
什么是inline方法贱勃?使用inline修飾的方法和普通方法有什么區(qū)別井赌?這背后的原理是什么谤逼?這就是本篇文章要闡述的內(nèi)容。
先來看下沒有使用inline修飾的方法仇穗,如下所示:
class Test {
//定義了一個(gè)普通的方法m1流部,該方法打印語句hello world
fun m1() {
println("hello world")
}
//測(cè)試方法test,僅僅調(diào)用m1
fun test() {
m1()
}
}
上面代碼就是一個(gè)普通的代碼調(diào)用纹坐,來看看其背后的字節(jié)碼實(shí)現(xiàn)(這里主要來看下test方法的實(shí)現(xiàn)):
public final test()V
L0
LINENUMBER 13 L0
ALOAD 0
//注意這里枝冀,編譯器會(huì)通過字節(jié)碼指令I(lǐng)NVOKEVIRTUAL
//來完成方法m1的調(diào)用
INVOKEVIRTUAL Test.m1 ()V
L1
LINENUMBER 15 L1
RETURN
L2
LOCALVARIABLE this LTest; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
上面注釋已經(jīng)說明了普通方法的調(diào)用流程,即會(huì)通過字節(jié)碼指令完成方法的調(diào)用耘子,那么inline方法會(huì)有什么不同呢果漾?看個(gè)inline方法的示例:
class Test {
//此時(shí)方法m1就是內(nèi)聯(lián)方法,使用了inline關(guān)鍵字修飾
inline fun m1() {
println("hello world")
}
//測(cè)試方法
fun test() {
m1()
}
}
上述代碼僅僅將方法m1使用了inline關(guān)鍵字來修飾谷誓,其他什么都沒有變绒障,來看看其背后對(duì)應(yīng)的字節(jié)碼:
public final test()V
L0
LINENUMBER 13 L0
ALOAD 0
ASTORE 1
L1
LINENUMBER 101 L1
LDC "hello world"http://注意這里
ASTORE 2
L2
//同樣也注意這里
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L3
L4
LINENUMBER 102 L4
NOP
//省略部分字節(jié)碼
從字節(jié)碼可以發(fā)現(xiàn),inline方法被直接編譯到了調(diào)用處片林,是作為調(diào)用方法的一部分來實(shí)現(xiàn)的端盆,而不是通過方法調(diào)用來完成的怀骤,這就是inline方法和普通方法的區(qū)別费封!
那么,為什么要這么做呢蒋伦?答案是顯而易見的弓摘,因?yàn)榉椒ㄕ{(diào)用是有性能開銷的,而inline方法剛好可以將方法調(diào)用編譯到自己的方法體實(shí)現(xiàn)中痕届,故節(jié)省了很多開銷韧献。
如果,你覺得方法開銷不必在意的話研叫,那么就來看下面一段代碼:
class Test {
//方法m0接收了一個(gè)方法類型作為參數(shù)
fun m0(checkStr: (str: String) -> String) {
val str1 = "test str"
println(checkStr(str1))
}
fun test() {
m0({ "test2" })
}
}
如果看過kotlin入門潛修之進(jìn)階篇—高階方法和lambda表達(dá)式
這篇文章锤窑,一定對(duì)上面的代碼不陌生,這就是kotlin中的高階方法嚷炉!在kotlin入門潛修之進(jìn)階篇—高階方法和lambda表達(dá)式原理
這篇文章中渊啰,我們?cè)治鲞^,方法類型實(shí)際上最終都是以對(duì)象的形式存在的申屹,kotlin會(huì)為lambda表達(dá)式生成一個(gè)新類绘证,并通過該類的實(shí)例完成方法的入?yún)⒓皥?zhí)行。所以這中間存在著一些內(nèi)存方面的開銷哗讥,如果有很多這種語句嚷那,那么這個(gè)開銷將會(huì)變的非常之大。
下面我們將m0方法改成inline方法進(jìn)行實(shí)現(xiàn)杆煞,源代碼如下所示:
class Test {
//m0方法使用了inline關(guān)鍵字來修飾魏宽,表示m0是個(gè)內(nèi)聯(lián)方法
inline fun m0(checkStr: (str: String) -> String) {
val str1 = "test str"
println(checkStr(str1))
}
fun test() {
m0({ "test2" })
}
}
上面代碼將m0標(biāo)注為了inline方法腐泻,來看下其背后對(duì)應(yīng)的字節(jié)碼,如下所示:
// ================Test.class =================
// class version 50.0 (50)
// access flags 0x31
public final class Test {
// access flags 0x11
// signature (Lkotlin/jvm/functions/Function1<-Ljava/lang/String;Ljava/lang/String;>;)V
// declaration: void m0(kotlin.jvm.functions.Function1<? super java.lang.String, java.lang.String>)
public final m0(Lkotlin/jvm/functions/Function1;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1
LDC "checkStr"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 3 L1
LDC "test str"
ASTORE 3
L2
LINENUMBER 4 L2
ALOAD 1
ALOAD 3
INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object;
ASTORE 4
L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 4
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
L5
LINENUMBER 5 L5
RETURN
L6
LOCALVARIABLE str1 Ljava/lang/String; L2 L6 3
LOCALVARIABLE this LTest; L0 L6 0
LOCALVARIABLE checkStr Lkotlin/jvm/functions/Function1; L0 L6 1
LOCALVARIABLE $i$f$m0 I L0 L6 2
MAXSTACK = 2
MAXLOCALS = 5
// access flags 0x11
public final test()V
L0
LINENUMBER 8 L0
ALOAD 0
ASTORE 1
L1
LINENUMBER 95 L1
LDC "test str"
ASTORE 2
L2
LINENUMBER 96 L2
ALOAD 2
ASTORE 3
L3
LINENUMBER 8 L3
LDC "test2"
L4
L5
ASTORE 3
L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 3
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L7
L8
LINENUMBER 97 L8
NOP
L9
LINENUMBER 9 L9
RETURN
L10
LOCALVARIABLE it Ljava/lang/String; L3 L5 3
LOCALVARIABLE $i$a$1$m0 I L3 L5 4
LOCALVARIABLE str1$iv Ljava/lang/String; L2 L9 2
LOCALVARIABLE this_$iv LTest; L1 L9 1
LOCALVARIABLE $i$f$m0 I L1 L9 5
LOCALVARIABLE this LTest; L0 L10 0
MAXSTACK = 2
MAXLOCALS = 6
// access flags 0x1
public <init>()V
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
沒錯(cuò)湖员,上面就是生成的全部字節(jié)碼贫悄!通過字節(jié)碼可以發(fā)現(xiàn)以下兩點(diǎn):
- kotlin不再為lambda表達(dá)式生成一個(gè)新類。
- m0方法的實(shí)現(xiàn)會(huì)被編譯到其調(diào)用處(即test方法)中娘摔。
由此可見窄坦,inline方法會(huì)節(jié)省掉使用lambda或者匿名方法時(shí)所帶來的內(nèi)存開銷。這就是inline方法背后的優(yōu)勢(shì)凳寺。
noinline
當(dāng)inline作用于方法時(shí)鸭津,會(huì)同時(shí)對(duì)方法本身以及傳入的lambda起作用,換句話說肠缨,inline方法和lambda都會(huì)被編譯到方法的調(diào)用處(可以見上例)逆趋,那么如果我們只需要一部分方法被內(nèi)聯(lián)該如果做呢?這就是noinline關(guān)鍵字的作用晒奕!使用noinline關(guān)鍵字表明其修飾的部分不需要內(nèi)聯(lián)到調(diào)用處闻书,如下所示:
//代碼同上個(gè)例子基本一致
class Test {
//唯一不一樣的地方就是我們使用noinline修飾了m0方法
//的類型入?yún)? inline fun m0(noinline checkStr: (str: String) -> String) {
val str1 = "test str"
println(checkStr(str1))
}
//測(cè)試方法
fun test() {
m0({ "test2" })
}
}
那么這么寫以后,kotlin會(huì)怎么處理呢脑慧?通過查看字節(jié)碼可知魄眉,kotlin會(huì)忽略加在方法開頭的inline修飾符,而照例為傳入的lambda表達(dá)式生成了一個(gè)新類闷袒!但是m0方法體中的實(shí)現(xiàn)卻被內(nèi)聯(lián)到了test方法中坑律,字節(jié)碼摘錄如下:
//test方法對(duì)應(yīng)的字節(jié)碼
public final test()V
L0
LINENUMBER 8 L0
ALOAD 0
ASTORE 1
GETSTATIC Test$test$1.INSTANCE : LTest$test$1;
CHECKCAST kotlin/jvm/functions/Function1
ASTORE 2
L1
LINENUMBER 95 L1
LDC "test str"http://由此可知,m0方法體中代碼被內(nèi)聯(lián)到了此處
ASTORE 3
L2
LINENUMBER 96 L2
ALOAD 2
ALOAD 3
//這里囊骤,可以看出晃择,是通過方法調(diào)用來完成lambda表達(dá)式功能的
INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object;
ASTORE 4
L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 4
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
//下面是kotlin為lambda表達(dá)式生成的新類
final class Test$test$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function1 {
//省略了類中的內(nèi)容
}
非局部返回(Non-local returns)
先來看個(gè)例子:
//定義了一個(gè)高階方法m0,該方法接收一個(gè)方法類型
fun m0(param: () -> Unit) {
}
//測(cè)試方法
fun test() {
//調(diào)用m0方法
m0 {
return// !!!錯(cuò)誤也物,編譯不通過!
}
}
上述代碼將會(huì)編譯不通過宫屠!原因是return一般會(huì)結(jié)束方法,表示方法執(zhí)行完成滑蚯,而lambda表達(dá)式則無法結(jié)束方法浪蹂,也就是不能在lambda表達(dá)式中使用return語句。那么如果我們想要結(jié)束lambda的執(zhí)行該如何做呢膘魄?那就是使用label機(jī)制乌逐,如下所示:
fun test() {
m0 {
return@m0//通過隱式的label返回
}
}
隱式的label就是不給m0指定label標(biāo)識(shí),而是通過默認(rèn)的方法名m0進(jìn)行返回创葡。需要注意浙踢,上面的return@m0是一個(gè)整體,不能有任何空格灿渴。使用label后就可以從lambda中返回洛波,然后繼續(xù)執(zhí)行方法體下面的語句胰舆。
那么如果不使用label還有什么辦法嗎?有蹬挤,使用inline方法即可缚窿,如下所示:
//將m0標(biāo)識(shí)為了inline方法
inline fun m0(param: () -> Unit) {
}
//測(cè)試方法
fun test() {
m0 {
return//正確!
}
}
為什么inline方法可以運(yùn)行return呢焰扳?這是正是因?yàn)閕nline方法是會(huì)被編譯到調(diào)用處的方法體中倦零,所以可以使用return。但這也同時(shí)意味著吨悍,return語句會(huì)直接結(jié)束掉整個(gè)方法的執(zhí)行扫茅,而不會(huì)再執(zhí)行后面的語句。
這也是使用return@label和使用inline return的區(qū)別:前者僅僅是從當(dāng)前l(fā)abel作用域返回育瓜,后者則會(huì)返回整個(gè)方法葫隙。
使用inline方法返回的這種形式,就被稱為非局部返回躏仇。其定義為:位于lambda表達(dá)式內(nèi)恋脚,但是可以通過return直接結(jié)束其所屬方法的執(zhí)行。
inline修飾的方法都可以用作非局部返回焰手。kotlin標(biāo)準(zhǔn)庫中有很多inline方法糟描,如最常用的forEach方法。這些方法都可以用在非局部返回場(chǎng)景中册倒。
//kotlin庫定義的forEach方法蚓挤,是個(gè)inline方法
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
舉個(gè)forEach非局部返回的使用場(chǎng)景:比如我們需要實(shí)現(xiàn)遇到偶數(shù)就結(jié)束方法運(yùn)行的功能磺送,如下所示:
fun test() {
listOf(1, 2, 3).forEach {
if (it % 2 == 0) {//當(dāng)遇到偶數(shù)時(shí)就直接返回
println(it)
return
}
println(it)
}
}
上面代碼會(huì)打印1 2驻子。即當(dāng)遇到偶數(shù)時(shí)就直接返回了整個(gè)方法。如果forEach不是inline方法估灿,則無法這么使用崇呵。
crossinline
來看一個(gè)例子:
//定義了一個(gè)inline高階方法m0,接收一個(gè)方法類型作為入?yún)? inline fun m0(checkStr: () -> Unit) {
object : Runnable {
override fun run() {
checkStr()//!!!編譯錯(cuò)誤
}
}.run()
}
上面代碼的場(chǎng)景是這樣的:我們不是在inline方法體中直接使用lambda表達(dá)式馅袁,而是在其方法體內(nèi)部的匿名對(duì)象中使用了lambda表達(dá)式域慷。這種場(chǎng)景編譯器會(huì)報(bào)錯(cuò),不允許這么做汗销!其實(shí)想想也是有道理的犹褒,因?yàn)槿绻试ScheckStr非局部返回的話,那么就會(huì)直接結(jié)束整個(gè)run方法弛针,這可能會(huì)帶來難以預(yù)料的錯(cuò)誤叠骑。針對(duì)這種情況kotlin提供了一個(gè)關(guān)鍵字crossinline,使用crossinline修飾的lambda表達(dá)式可以應(yīng)用于方法體的任何地方削茁,如下所示:
inline fun m0(crossinline checkStr: () -> Unit) {
object : Runnable {
override fun run() {
//由于checkStr被使用了crossinline關(guān)鍵字修飾
//所以可以這么用
checkStr()
println("end")
}
}.run()
}
具體化類型參數(shù)(Reified type parameters)
kotlin中也有反射宙枷,kotlin允許使用反射來訪問類型信息掉房,示例如下:
//根據(jù)類型獲取對(duì)應(yīng)的值,如果匹配我們的類型則返回對(duì)應(yīng)類型的默認(rèn)值
fun <T> getIntDefValByType(clazz: Class<T>): T? {
val defVal = 0//Int的默認(rèn)值為0
if (clazz.isInstance(defVal)) {//如果傳入的是Int則返回defVal
return defVal as T?
}
return null//如果不是Int則返回null
}
//測(cè)試方法main
fun main(args: Array<String>) {
println(getIntDefValByType(Int::class.javaObjectType))//打印 0
println(getIntDefValByType(String::class.javaObjectType))//打印null
}
上面方法的功能是根據(jù)獲取Int類型的默認(rèn)值慰丛,如果是類型匹配則返回其默認(rèn)值0卓囚,否則返回null。::class表示獲取kotlin中的類型信息诅病,而::class.javaObjectType表示獲取java對(duì)應(yīng)的類型信息(也可以使用::class.java哪亿,但是::class.java無法返回包裹類型對(duì)應(yīng)的類型信息,而javaObjectType可以)贤笆,這個(gè)方法不具有實(shí)際意義锣夹,但是說明kotlin支持使用反射來進(jìn)行類型校驗(yàn)。
上述代碼雖然能完成功能苏潜,但是使用起來不太優(yōu)雅银萍,我們還要傳入具體的類型,那么有沒有更優(yōu)雅的使用方法恤左?當(dāng)然有贴唇,這就是使用reified關(guān)鍵字。示例如下:
//方法需要使用inline修飾飞袋,加上了reified 關(guān)鍵字戳气,
//此時(shí)方法不需要Class類型的入?yún)?inline fun <reified T> getIntDefValByType2(): T? {
val defVal = 0
if (defVal is T) {//在這里判斷是否是T類型即可
return defVal
}
return null
}
//測(cè)試方法main
fun main(args: Array<String>) {
println(getIntDefValByType2<Int>())//打印 0
println(getIntDefValByType2<String>())//打印 null
}
上面就是reified的用法,其對(duì)應(yīng)的調(diào)用形式就顯得相對(duì)優(yōu)雅些巧鸭。最后需要注意的是瓶您,reified關(guān)鍵字只能用于inline方法中!
那么reified背后的原理是什么纲仍?為什么我們使用reified就無法進(jìn)行類型判斷呀袱?
對(duì)于泛型,前面kotlin入門潛修之類和對(duì)象篇—泛型及其原理這篇文章已經(jīng)闡述的很詳細(xì):kotlin中的泛型同java一樣郑叠,都會(huì)在運(yùn)行期進(jìn)行類型擦除夜赵,所以我們使用"is T"這種方法來判斷類型的時(shí)候是不可能的,因?yàn)檫\(yùn)行時(shí)根本就不存在所謂的T類型乡革。那么reified關(guān)鍵字背后又做了什么寇僧?
我們來看下reified的背后的字節(jié)碼實(shí)現(xiàn),首先先把源代碼粘貼出來沸版,如下所示:
//要分析的源代碼
inline fun <reified T> getIntDefValByType2(): T? {
val defVal = 0
if (defVal is T) {
return defVal
}
return null
}
//測(cè)試代碼
fun main(args: Array<String>) {
getIntDefValByType2<Int>()//該語句符合getIntDefValByType2所需類型Int(defVal類型)
getIntDefValByType2<String>()//該語句不符合getIntDefValByType2所需的類型Int(defVal類型)
在來看下其對(duì)應(yīng)的字節(jié)碼嘁傀,這里摘錄重點(diǎn)的一部分,如下所示:
//這里只摘錄了main方法中對(duì)應(yīng)的一部分代碼视粮,
LINENUMBER 121 L9
ILOAD 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
INSTANCEOF java/lang/String
IFEQ L10
上面字節(jié)碼摘錄了main方法對(duì)應(yīng)的字節(jié)碼细办,其實(shí)main方法總共就兩句源代碼,一句是getIntDefValByType2<Int>()馒铃,一句是getIntDefValByType2<String>()蟹腾。因?yàn)間etIntDefValByType2方法是inline的痕惋,所以getIntDefValByType2的實(shí)現(xiàn)會(huì)被編譯到main方法當(dāng)中。但是通過字節(jié)碼發(fā)現(xiàn)娃殖,getIntDefValByType2<String>()語句會(huì)加上一句類型判斷值戳,即上面粘貼出來的字節(jié)碼:INSTANCEOF java/lang/String。而getIntDefValByType2<Int>卻沒有對(duì)應(yīng)的字節(jié)碼語句炉爆。這說明了什么堕虹?
是時(shí)候總結(jié)下reified背后的原理了。reified實(shí)際上是作用在編譯期間的芬首,由于reified必須用于inline方法中赴捞,而對(duì)于inline方法實(shí)際上是編譯到當(dāng)前代碼的調(diào)用處,所以在編譯的時(shí)候編譯器就能根據(jù)defVal來確認(rèn)其對(duì)應(yīng)的具體類型了郁稍。當(dāng)T被傳入多個(gè)類型時(shí)(比如Int赦政、String等等),kotlin就會(huì)在編譯的時(shí)候插入INSTANCEOF字節(jié)碼指令進(jìn)行類型判斷耀怜,INSTANCEOF指令會(huì)檢測(cè)是否是指定類的實(shí)例恢着,在本例中,如果是則返回defVal财破,否則返回null掰派。而在INSTANCEOF判斷之前,還有一句字節(jié)碼指令: INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
這句指令表示將Int的值轉(zhuǎn)為java下的Integer類型左痢,并獲取其值靡羡,這樣才有后面INSTANCEOF指令的判斷。
內(nèi)聯(lián)屬性(inline properties)
從kotlin1.1之后俊性,inline關(guān)鍵字可以用于沒有后備字段的屬性上略步,如下所示:
class Test {
val t0: Int = 2//正確,普通的常量(變量定義)
val t1: Int//正確磅废,可以使用inline修飾get
inline get() = 1
var t2: Int//正確
inline get() = 1
set(value) {}
var t3: Int//正確
inline get() = 3
inline set(value) {}
var t4: Int//正確
get() = 1
set(value) {}
var t5: Int//!!!錯(cuò)誤纳像,這里顯示使用了field字段
inline get() = 3
inline set(value) {
field = value
}
inline val t6 = 1//!!!錯(cuò)誤荆烈,這里默認(rèn)使用了field字段
inline var t7 = 2//!!!錯(cuò)誤拯勉,這里默認(rèn)使用了field字段
inline var t8: Int//!!!正確,我們復(fù)寫了get和set憔购,就沒有了field字段
get() = 1
set(value) {}
}
上述代碼比較容易理解宫峦,只需要記住只要有field字段就無法使用inline關(guān)鍵字即可。關(guān)于什么時(shí)候有field字段玫鸟,什么時(shí)候沒有导绷,可記住一句話:只有使用默認(rèn)的getter(setter)以及顯示使用field字段的時(shí)候,后備字段才會(huì)存在屎飘。具體可參見另一篇文章:kotlin入門潛修之類和對(duì)象篇—屬性和字段
那么inline屬性有什么作用妥曲?答案是顯而易見的:inline屬性同inline方法一樣會(huì)被編譯器編譯到其調(diào)用處贾费,避免了一些開銷。
公有內(nèi)聯(lián)方法的限制
使用inline方法會(huì)有一個(gè)潛在的問題檐盟,那就是當(dāng)一個(gè)模塊調(diào)用其他模塊的公有inline方法時(shí)褂萧,由于inline方法會(huì)被編譯到調(diào)用處,所以可能會(huì)存在其他模塊方法變更葵萎,而當(dāng)前模塊沒有重新編譯的問題导犹。這種問題主要是使用了非公有的inline方法引起的(即公有的inline方法調(diào)用了非公有的inline方法),所以kotlin就限制公有的inline方法不能調(diào)用非公有的inline方法羡忘。如下所示:
//私有inline方法
private inline fun m0() {}
//默認(rèn)為public的共有inline方法
inline fun m1() {}
//internal inline方法
internal fun m2() {}
//使用@PublishedApi修飾的internal inline方法
@PublishedApi
internal fun m3() {}
//測(cè)試方法
inline fun test() {
m0()//錯(cuò)誤谎痢,public inline方法無法調(diào)用private inline方法
m1()//正確,public inline方法可以調(diào)用public inline方法
m2()//錯(cuò)誤卷雕,public inline方法無法調(diào)用internal inline方法
m3()//正確节猿,public inline方法可以調(diào)用使用@PublishedApi注解標(biāo)注的internal inline方法,具體見下面解釋漫雕。
}
這里所說的非公有的inline方法是指使用private和internal修飾的方法沐批。而對(duì)于internal修飾的方法,如果用戶使用了@PublishedApi注解進(jìn)行了標(biāo)識(shí)蝎亚,則可以被public inline方法使用九孩,因?yàn)槭褂聾PublishedApi注解標(biāo)識(shí)的方法,同public修飾的inline方法一樣,編譯器會(huì)在編譯的時(shí)候會(huì)進(jìn)行檢查发框。