Kotlin高階函數(shù)詳解

高階函數(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ù)對比

高階函數(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

  • applythis值作為接收者調(diào)用指定的函數(shù)[block]捂蕴,并返回this值譬涡。

  • alsothis值作為參數(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)存開銷


參考

朱濤 · Kotlin 編程第一課
Kotlin 高階函數(shù)詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩憾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妄帘,老刑警劉巖楞黄,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抡驼,居然都是意外死亡鬼廓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門致盟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碎税,“玉大人,你說我怎么就攤上這事馏锡±柞澹” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵眷篇,是天一觀的道長萎河。 經(jīng)常有香客問我,道長蕉饼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任玛歌,我火速辦了婚禮昧港,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘支子。我一直安慰自己创肥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布值朋。 她就那樣靜靜地躺著叹侄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昨登。 梳的紋絲不亂的頭發(fā)上趾代,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音丰辣,去河邊找鬼撒强。 笑死,一個胖子當(dāng)著我的面吹牛笙什,可吹牛的內(nèi)容都是我干的飘哨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼琐凭,長吁一口氣:“原來是場噩夢啊……” “哼芽隆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胚吁,失蹤者是張志新(化名)和其女友劉穎臼闻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囤采,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡述呐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕉毯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乓搬。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖代虾,靈堂內(nèi)的尸體忽然破棺而出进肯,到底是詐尸還是另有隱情,我是刑警寧澤棉磨,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布江掩,位于F島的核電站,受9級特大地震影響乘瓤,放射性物質(zhì)發(fā)生泄漏环形。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一衙傀、第九天 我趴在偏房一處隱蔽的房頂上張望抬吟。 院中可真熱鬧,春花似錦统抬、人聲如沸火本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钙畔。三九已至,卻和暖如春金麸,著一層夾襖步出監(jiān)牢的瞬間擎析,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工钱骂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叔锐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓见秽,卻偏偏與公主長得像愉烙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子解取,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容