高階函數(shù)是Kotlin函數(shù)式編程的基石,各種開源框架的關(guān)鍵元素跌造,掌握了高階函數(shù)對一些框架的源代碼更容易理解杆怕,對學(xué)習(xí)Jetpack Compose也變得得心應(yīng)手。
了解高階函數(shù)
可以先看View.java中點擊事件的代碼壳贪,分析下:
- 首先陵珍,為了設(shè)置點擊事件的監(jiān)聽,代碼里特地定義了一個 OnClickListener 接口违施;
- 接著互纯,為了設(shè)置鼠標點擊事件的監(jiān)聽,又專門定義了一個 OnContextClickListener 接口磕蒲。
這里如果借助高階函數(shù)留潦,可以一個接口都不定義。
//View.java
//成員變量
private OnClickListenermOnClickListener;
private OnContextClickListenermOnContextClickListener;
//監(jiān)聽手指點擊事件
public void setOnClickListener(OnClickListenerl){
mOnClickListener=l;
}
//為傳遞這個點擊事件亿卤,專門定義了一個接口
public interface OnClickListener{
voidon Click(Viewv);
}
//監(jiān)聽鼠標點擊事件
public void setOnContextClickListener(OnContextClickListenerl){
getListenerInfo().mOnContextClickListener=l;
}
//為傳遞這個鼠標點擊事件愤兵,專門定義了一個接口
public interface OnContextClickListener{
booleanonContextClick(Viewv);
}
看上段代碼的Kotlin等價代碼:
//View.kt
// (View)->Unit就是「函數(shù)類型」
// ↑ ↑
var mOnClickListener:((View)->Unit)? = null
var mOnContextClickListener:((View)->Unit)? = null
//高階函數(shù)
fun setOnClickListener(l:(View)->Unit){
mOnClickListener=l;
}
//高階函數(shù)
fun setOnContextClickListener(l:(View)->Unit){
mOnContextClickListener=l;
}
通過對比,我們可以發(fā)現(xiàn)Kotlin引入高階函數(shù)排吴,實際分為兩個部分:
- 用函數(shù)類型替代接口定義秆乳;
- 用 Lambda 表達式作為函數(shù)參數(shù)。
使用高階函數(shù)好處
也帶來了幾個好處:一個是針對定義方钻哩,代碼中減少了兩個接口類的定義屹堰;另一個是對于調(diào)用方來說,代碼也會更加簡潔街氢。就大大減少了代碼量扯键,提高了代碼可讀性,并通過減少類的數(shù)量珊肃,提高了代碼的性能荣刑。
高階函數(shù)的相關(guān)概念
函數(shù)類型
函數(shù)類型(Function Type)就是函數(shù)的類型
在Kotlin中,函數(shù)是一等公民(first class)伦乔,這意味著函數(shù)可以被存儲在變量或者數(shù)據(jù)結(jié)構(gòu)中厉亏,它是有類型的。Kotlin使用函數(shù)類型來描述一個函數(shù)的具體類型烈和。一個完整語法的函數(shù)類型如下:
//(x:Int, y:Int) -> Int 這個就是 sum 函數(shù)的類型
// ↑ ↑ ↑
fun sum(a: Int, b: Int):Int{
return (a+b)
}
(Int, Int) ->Int 就代表了參數(shù)類型是兩個 Int爱只,返回值類型為 Int 的函數(shù)類型。
函數(shù)引用
函數(shù)的引用招刹,普通的變量也有引用的概念恬试,我們可以將一個變量賦值給另一個變量窝趣。而這一點,在函數(shù)上也是同樣適用的训柴,函數(shù)也有引用哑舒,并且也可以賦值給變量。如:作為參數(shù)傳遞給高階函數(shù)畦粮。
//函數(shù)賦值給變量 函數(shù)引用
// ↑ ↑
val function: (Int, Int) ->Int = ::sum
高階函數(shù)
定義:高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)散址。
其實Android里的點擊事件監(jiān)聽用Kotlin來實現(xiàn)的話乖阵,它就是一個高階函數(shù)宣赔。
// 函數(shù)作為參數(shù)的高階函數(shù)
// ↓
fun setOnClickListener(l: (View) -> Unit) { ... }
// 返回值是函數(shù)類型的高階函數(shù)
// ↓
fun higherFunction(): (Int, Int) -> Int { ... }
綜上可以理解一個函數(shù)的參數(shù)或是返回值,它們當(dāng)中有一個是函數(shù)的情況下瞪浸,這個函數(shù)就是高階函數(shù)儒将。
Lambda
上面講到函數(shù)的引用這種方法可以給函數(shù)類型賦值,也可以通過Lambda表達式對一個函數(shù)類型的變量進行賦值(大多數(shù)情況都是使用Lambda表達式來調(diào)用高階函數(shù))如下所示:
val function: (Int, Int) ->Int = {num1: Int, num2: Int -> num1 + num2}
Lambda 表達式語法結(jié)構(gòu):{參數(shù)名1: 參數(shù)類型, 參數(shù)名2: 參數(shù)類型 -> 函數(shù)體} 函數(shù)體中可以編寫任意行代碼对蒲,最后一行代碼會自動作為 Lambda 表達式的返回值
高階函數(shù)的調(diào)用示例
這里用forEach函數(shù)來舉例钩蚊,forEach函數(shù)也是一個高階函數(shù)。源碼如下:
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
intArray.forEach(?) //此處蹈矮? 是個函數(shù)類型的參數(shù)砰逻,函數(shù)類型是 (Int) -> Unit
//函數(shù)類型的參數(shù)是可以定義相同的函數(shù)類型的變量傳給forEach,如下:
val action: (Int) -> Unit = ??
fun main() {
intArray.forEach(action)
}
上述我們講到了函數(shù)賦值可以使用函數(shù)引用也可以使用 Lambda 表達式泛鸟,使用函數(shù)引用代碼如下:
val action: (Int) -> Unit = ::printValue
fun main() {
intArray.forEach(action)
}
fun printValue(value: Int): Unit {
println(value)
}
函數(shù)引用的方式調(diào)用高階函數(shù)太過麻煩蝠咆,還需要特地寫一個函數(shù)來調(diào)用高階函數(shù)。所以我們絕對多數(shù)情況都是使用Lambda 表達式來調(diào)用高階函數(shù)的北滥,且Lambda 表達式有很多簡潔的寫法刚操。
使用Lambda 表達式來改寫上述代碼:
val action: (Int) -> Unit = {value: Int -> println(value)}
fun main() {
intArray.forEach(action)
}
1、Kotlin 有類型推到機制再芋,所以 Int 可以去掉
val action: (Int) -> Unit = {value -> println(value)}
2菊霜、Lambda 表達式如果只有一個參數(shù),可以直接用 it 來代替济赎,并且不需要聲明參數(shù)名
val action: (Int) -> Unit = {println(it)}
//將簡化后的代碼代入鉴逞,現(xiàn)在上述的代碼就變成如下這樣
fun main() {
intArray.forEach({println(it)})
}
3、當(dāng)Lambda 參數(shù)是函數(shù)的最后一個參數(shù)時司训,可以將 Lambda 表達式移到函數(shù)括號的外面构捡。
fun main() {
intArray.forEach(){
println(it)
}
}
4、Lambda 表達式是函數(shù)的唯一一個參數(shù)的話豁遭,還可以將函數(shù)的括號省略
fun main() {
intArray.forEach{
println(it)
}
}
帶接收者的函數(shù)類型
這里拿 Kotlin 的標準函數(shù) apply函數(shù)和also函數(shù)來分析:
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
仔細?較這兩個擴展函數(shù)會發(fā)現(xiàn)它們?常相似:它們都接收?個 block 函數(shù)并返回 this 值叭喜。
唯?的差別就在于apply的 block 函數(shù)類型為:T.() -> Unit,?also的 block 函數(shù)類型為:(T) -> Unit)
T.() -> Unit和(T) -> Unit) 區(qū)別
看下圖apply和also使用可以得出:
在also方法中接收到的是it蓖谢,而在apply方法中接收到的是this
apply以this值作為接收者調(diào)用指定的函數(shù)[block]捂蕴,并返回this值譬涡。
also以this值作為參數(shù)調(diào)用指定的函數(shù)[block],并返回this值啥辨。
-
T.()->Unit 的函數(shù)體中可以直接使用T代表的對象涡匀,即用this代表對象
(T) -> Unit 將T表示的對象作為實參通過函數(shù)參數(shù)傳遞進來,供函數(shù)體使用
()->Unit與T表示的對象沒有直接聯(lián)系溉知,只能通過外部T實例的變量來訪問對象
apply和also使用區(qū)別
總結(jié)
- 將函數(shù)的參數(shù)類型和返回值類型抽象出來后陨瘩,我們就得到了函數(shù)類型。比如(View) ->Unit 就代表了參數(shù)類型是 View级乍,返回值類型為 Unit 的函數(shù)類型舌劳。
- 如果一個函數(shù)的“參數(shù)”或者“返回值”的類型是函數(shù)類型,那這個函數(shù)就是高階函數(shù)玫荣。
- Lambda 就是函數(shù)的一種簡寫甚淡,但存在運行時帶來的額外內(nèi)存開銷,可以使用inline關(guān)鍵字來修飾函數(shù)捅厂,使其變?yōu)閮?nèi)聯(lián)函數(shù)贯卦,可提升性能。
Kotlin官方的源代碼Standard.Kt可以去分析其中的 with焙贷、let撵割、also、takeIf辙芍、repeat啡彬、apply,來進一步加深對高階函數(shù)的理解沸手。還有Collections.Kt外遇,可以去分析其中的 map、flatMap契吉、fold跳仿、groupBy 等操作符,從而對高階函數(shù)的應(yīng)用場景有一個更具體的認知捐晶。
關(guān)鍵字 inline「內(nèi)聯(lián)函數(shù)」
Kotlin 中新增了「內(nèi)聯(lián)函數(shù)」菲语,內(nèi)聯(lián)函數(shù)起初是在 C++ 里面的。當(dāng)一個函數(shù)被內(nèi)聯(lián) inline 標注后惑灵,在調(diào)用它的地方山上,會把這個函數(shù)方法體中的所以代碼移動到調(diào)用的地方,而不是通過方法間壓棧進棧的方式英支。
內(nèi)聯(lián)函數(shù)可以消除Lambda表達式運行時帶來的額外內(nèi)存開銷