2 回答

TA貢獻(xiàn)1824條經(jīng)驗(yàn) 獲得超8個(gè)贊
1. Kotlin 中的 Lambda 表達(dá)式
如果你已經(jīng)開始使用 Koltin, 或者對(duì)它有過一些了解的話,那么一定對(duì)這種寫法并不陌生了:
// 代碼一:Kotlin 代碼view.setOnClickListener{println("click")}1234
它跟下面這段 Java 代碼是等價(jià)的:
// 代碼二:java 代碼view.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) {System.out.println("click");}});1234567
和 Java8 一樣,Kotlin 是支持 Lambda 表達(dá)式的,如代碼一所示,就是 Lambda 的一個(gè)具體應(yīng)用。
可見,使用 lambda 減少了很多冗余,使代碼寫起來更簡(jiǎn)潔優(yōu)雅,讀起來也更順暢自然了。
但是,你有沒有想過,為什么 Kotlin 可以這樣寫,這里為什么可以使用 lambda ?
2. 為什么可以這么寫?
在 Kotlin 中,一個(gè) Lambda 就是一個(gè)匿名函數(shù)。
代碼一其實(shí)是對(duì)下面代碼三的簡(jiǎn)寫:
// 代碼三:Kotlin 代碼view.setOnClickListener({v -> println("click")})1234
之所以簡(jiǎn)寫成代碼一的樣子,是基于這兩點(diǎn)特性:
如果 lambda 是一個(gè)函數(shù)的唯一參數(shù),那么調(diào)用這個(gè)函數(shù)時(shí)可以省略圓括號(hào)
如果 lambda 所表示的匿名函數(shù)只有一個(gè)參數(shù),那么可以省略它的聲明以及->符號(hào)(默認(rèn)會(huì)用it來給省略的參數(shù)名命名)
// 代碼四:OnClickListener 接口在 java 中的定義public interface OnClickListener { void onClick(View v);
}1234
// 代碼五public class TestSAM {
SamType1 sam1,;
SamType2 sam2,; public void setSam(SamType1 sam1) { this.sam1 = sam1;
} public void setSam(SamType2 sam2) { this.sam2 = sam2;
} public interface SamType1 { void doSomething(int value);
} public interface SamType2 { void doSomething2(int value);
}
}123456789101112131415161718
// 代碼六:kotlin中調(diào)用,這段代碼是編譯不過的TestSAM().setSam {
println("dodo")
}1234
// 代碼七: 歧義消除// 方式一TestSAM().setSam (SamType1 { println("dodo") })
// 方式二TestSAM().setSam ({ println("dodo") } as SamType1)12345
// 代碼八: 使用一個(gè)實(shí)現(xiàn)接口的匿名類作為參數(shù)TestSAM().setSam(object : TestSAM.SamType1 { override fun doSomething(value: Int) {
println("dodo")
}
})123456
OK,從代碼三的結(jié)構(gòu)中,能夠更清晰的看出,這里的 view.setOnClickListener 函數(shù)是接收了一個(gè) lambda 作為參數(shù)。而在 Kotlin 中,什么樣的函數(shù)才能把lambda(也即另一個(gè)函數(shù))作為參數(shù)呢?—— 對(duì),就是高階函數(shù)。
什么是高階函數(shù)?
高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)。
這是 Kotlin 和 Java 的區(qū)別之一,java 中并沒有高階函數(shù)的支持(java8是有高階函數(shù)的)。當(dāng)我們?cè)?java 中需要用到類似的概念時(shí),通常的做法是傳遞一個(gè)匿名類作為參數(shù),然后實(shí)現(xiàn)其中的某些抽象方法 —— 就比如上面的代碼二。
事實(shí)上,如果在 Android Studio 中,從 Kotlin 的代碼查看 view.setOnClickListener 函數(shù)的定義,就會(huì)發(fā)現(xiàn),看到的函數(shù)簽名就是一個(gè)高階函數(shù)的定義:
函數(shù)簽名提示
如上圖,所看到函數(shù)簽名是:
public final fun setOnClickListener(l: ((v:View!)->Unit)!): Unit
當(dāng)然,因?yàn)榉椒ㄊ窃?Java 中定義的,所以它也列出了 Java 的聲明,是這樣:
public void setOnClickListener(OnClickListener l)
我們知道,Kotlin 跟 Java 的很多類型都有差異,所以它們?cè)诨ハ嗾{(diào)用的時(shí),會(huì)有一個(gè)按照對(duì)應(yīng)關(guān)系的轉(zhuǎn)換。
對(duì)于上面的對(duì) setOnClickListener 方法的轉(zhuǎn)換,別的地方都好理解,比較難懂的是,為什么會(huì)把參數(shù)從 OnClickListener 類型轉(zhuǎn)換成了 (View) -> Unit。
(View) -> Unit 是一個(gè)函數(shù)類型,它表示這樣一個(gè)函數(shù):接收1個(gè)View類型的參數(shù),返回Unit。
正是這個(gè)對(duì)參數(shù)類型的轉(zhuǎn)換,使得 setOnClickListener 方法在 Kotlin 中變成了一個(gè)高階函數(shù),這樣正是它之所以能夠使用 lambda 作為參數(shù)的原因。
而這種轉(zhuǎn)換,就是我們題目中所說到這篇文章的主角 —— SAM 轉(zhuǎn)換 (Single Abstract Method Conversions)。
3. 什么是 SAM 轉(zhuǎn)換?
好吧,說了這么多,終于到正題了。
SAM 轉(zhuǎn)換,即 Single Abstract Method Conversions,就是對(duì)于只有單個(gè)非默認(rèn)抽象方法接口的轉(zhuǎn)換 —— 對(duì)于符合這個(gè)條件的接口(稱之為 SAM Type ),在 Kotlin 中可以直接用 Lambda 來表示 —— 當(dāng)然前提是 Lambda 的所表示函數(shù)類型能夠跟接口的中方法相匹配。
而 OnClickListener 在 java 中的定義是這樣的:
—— 恰好它就是一個(gè)符合條件的 SAM Type,onClick 函數(shù)的類型即是 (View) -> Unit。所以,在 Kotlin 中,能夠用 lambda 表達(dá)式 { println("click")} 來代替 OnClickListener 作為 setOnClickListener 函數(shù)的參數(shù)。
4. SAM 轉(zhuǎn)換的歧義消除
SAM 轉(zhuǎn)換的存在,使得我們?cè)?Kotlin 中調(diào)用 java 的時(shí)候能夠更得心應(yīng)手了,它在大部分的時(shí)間都能工作的很好。
當(dāng)然,也偶爾會(huì)有例外,比如,考慮下面的這段代碼:
—— TestSAM 有兩個(gè)重載的 setSam 方法,—— 并且它們的參數(shù)( SamType1、SamType2 )都是 SAM Type 的接口。—— 并且 SamType1 跟 SamType2 的唯一抽象方法的函數(shù)類型都是 (Int) -> Unit 。
o(╯□╰)o
這種情況比較吊軌,但是還有有可能會(huì)出現(xiàn)的。這時(shí)候,如果在 Kotlin 中直接使用代碼一類似的方式,就會(huì)報(bào)錯(cuò)了:
會(huì)提示這里歧義,編譯器不知道這個(gè) Lambda 代表是 SamType1 跟 SamType2 中的哪一個(gè)接口。
解決的辦法就是手動(dòng)標(biāo)明 Lambda 需要代替的接口類型,有兩種方式可以來標(biāo)明:
當(dāng)然,也還有一種方法是不再使用 SAM 轉(zhuǎn)換的機(jī)制,而是直接使用一個(gè) SamType1 的實(shí)例作為參數(shù):
這種方法當(dāng)然也是可以的,只是跟 lambda 相比起來,就顯得不那么優(yōu)雅了(優(yōu)雅很重要?。。。?。
5. SAM 轉(zhuǎn)換的限制
SAM 轉(zhuǎn)換的限制主要有兩點(diǎn) :
5.1 只支持 java
即只適用與 Kotlin 中對(duì) java 的調(diào)用,而不支持對(duì) Kotlin 的調(diào)用
官方的解釋是 Kotlin 本身已經(jīng)有了函數(shù)類型和高階函數(shù)等支持,所以不需要了再去轉(zhuǎn)換了。
如果你想使用類似的需要用 lambda 做參數(shù)的操作,應(yīng)該自己去定義需要指定函數(shù)類型的高階函數(shù)。
5.2 只支持接口,不支持抽象類。
這個(gè)官方?jīng)]有多做解釋。
我想大概是為了避免混亂吧,畢竟如果支持抽象類的話,需要做強(qiáng)轉(zhuǎn)的地方就太多了。而且抽象類本身是允許有很多邏輯代碼在內(nèi)部的,直接簡(jiǎn)寫成一個(gè) Lambda 的話,如果出了問題去定位錯(cuò)誤的難度也加大了很多。
6. 總結(jié)
OK,講完了??偨Y(jié)起來就是 SAM 轉(zhuǎn)換就是 kotlin 在調(diào)用 java 代碼時(shí)能使用 Lambda 的原因。了解了其原理,能夠讓我們?cè)趯懘a更自如,在偶爾出問題的時(shí)候也能更好更快地解決。

TA貢獻(xiàn)1780條經(jīng)驗(yàn) 獲得超1個(gè)贊
kotlin java都是jvm上的語言,對(duì)于jvm來說,沒有java,沒有kotlin,這些語言,只有一個(gè)特定格式的 class文件,只要這個(gè)class文件的格式滿足jvm的格式標(biāo)準(zhǔn),jvm就可以運(yùn)行這份class文件,java 跟kotlin的語法不一樣,但是他們有不同的編譯器,不同的編譯器,會(huì)把對(duì)應(yīng)的源文件,轉(zhuǎn)換成標(biāo)準(zhǔn)的class格式,從我們看源代碼的方式來看,代碼是不一樣的,但是從jvm運(yùn)行的角度來看,都是標(biāo)準(zhǔn)的class文件格式,kotlin使用Lambda的語法,經(jīng)過編譯器的時(shí)候,會(huì)把這種語法轉(zhuǎn)換成標(biāo)準(zhǔn)的class格式就可以了,
- 2 回答
- 0 關(guān)注
- 976 瀏覽
添加回答
舉報(bào)