1 回答

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