kotlin入門潛修之進階篇—高階方法和lambda表達式

本文收錄于 kotlin入門潛修專題系列氧苍,歡迎學習交流适肠。

創(chuàng)作不易,如有轉載候引,還請備注侯养。

高階方法和lambda表達式

在kotlin中,方法是一等公民澄干。什么是一等公民逛揩?翻譯成編程語言對應的意思就是:kotlin中的方法同一般的變量一樣,可以作為方法參數(shù)麸俘、可以賦值給其他變量等等辩稽。

那么作為靜態(tài)強類型限制語言的kotlin是怎么做到呢?那就是通過提供方法類型(function types)以及l(fā)ambda表達式來達到目標的从媚。本篇文章將闡述kotlin中的這些特性逞泄。

高階方法

高階方法是指,那些入?yún)㈩愋桶椒愋突蛘叻祷刂凳莻€方法類型的方法拜效。

所以喷众,理解高階方法之前,必然要理解另外一個定義:方法類型(function types)紧憾。

什么是方法類型到千?其實方法類型和我們常見的數(shù)字類型、字符串類型等等是一樣的赴穗,都是用作定義變量的類型憔四。只不過這個類型看著并不像我們平常定義的其他類型那么容易理解。來看個例子:

fun sayHello(str: String, checkStr: (str: String) -> Boolean) {
    if (checkStr(str)) {
        println("pass...")
    } else {
        println("error...")
    }
}

上面代碼定義了一個方法sayHello般眉,這個方法唯一特別的地方就是該方法的第二個參數(shù)類型:checkStr: (str: String) -> Boolean 了赵。我們咋一看可能就懵掉了:這個是什么鬼類型,完全看不明白甸赃!其實柿汛,這個類型就是方法類型!

方法類型的定義如同普通方法的聲明一樣辑奈,可以有入?yún)⒁部梢杂蟹祷刂悼撩旅驷槍Ψ椒愋妥鰩c闡述:

  1. 方法類型中的參數(shù)類型需要使用()括號包裹,如(A, B)-> C中的A鸠窗、B都需要包裹在小括號中妓羊。當然如果沒有參數(shù)類型可以省略參數(shù)類型,如 ()->C就表示該方法類型沒有參數(shù)類型稍计。
  2. 方法類型中->后面的類型表示該方法類型的返回類型躁绸,如(A, B)-> C中的C表示方法的返回類型就是C。如果該方法類型沒有返回類型(即默認返回Unit),則必須顯示指定其返回類型為Unit净刮,也就是不能省略Unit剥哑。
  3. 方法類型可以有額外的接收類型(receive type,如果不明白可以回顧擴展方法那篇文章)淹父,如 A.(B) -> C株婴,該方法類型表示可以通過A對象來調用參數(shù)為B類型且返回值我C類型的方法。
  4. Suspending方法也是方法類型暑认,只不過比較特殊困介,這個會在kotlin協(xié)程相關的文章中進行闡述。
  5. 方法類型中的入?yún)㈩愋涂梢灾付▍?shù)名字蘸际,如(x:Int, y:Int)-> Int座哩,這在生成文檔時很有用處,增加了文檔的可讀性粮彤。
  6. 方法類型中的->符號是右結合的根穷,所以方法類型(Int) ->((Int) ->Int) 和(Int) -> (Int) -> Int表達的是同一個類型,但是和方法類型((Int) ->(Int)) ->Int是不同的导坟。
  7. 方法類型可以使用typealias關鍵字定義別名屿良,如:typealias checkStr = (str: String) -> Boolean

好了,總結了一大堆乍迄,只不過是在腦子里面留個大概的印象管引,如果只看一遍而沒有實踐士败,過不多久肯定會忘記的〈沉剑現(xiàn)在先回到前面一個例子中,我們定義了一個方法類型的入?yún)⒘陆敲慈绾问褂媚匮牵渴纠缦拢?/p>

//main測試方法
fun main(args: Array<String>) {
    val testStr1 = ""
    val testStr2 = "test"
    sayHello(testStr1, { str -> str.isNotEmpty() })//打印'error...'
    sayHello(testStr2, { str -> str.isNotEmpty() })//打印'pass...'
}

上面代碼的打印結果已經(jīng)在語句后面注釋出來了,主要關注方法類型是如何調用的饥臂。

同其他類型傳參一樣逊躁,調用含有方法類型的方法時,必須要傳入一個方法類型的實例隅熙,那么該怎么實例化一個方法類型呢稽煤?實例化方法類型可以通過以下幾種方法:

  1. 使用字面量函數(shù)代碼塊,主要有一下兩種:
    (1)lamada表達式囚戚。如本例中的{str -> str.isNotEmpt()}就是采用這種方法酵熙。
    (2)匿名方法。如上面的例子中我們還可以這么調用:
sayHello(testStr2, fun(str): Boolean { return str.isNotEmpty()})
  1. 傳入已定義過的可調用的方法引用驰坊,這一類包括成員方法匾二、擴展方法、top-level方法甚至某些構造方法等,如上面例子我們可以直接傳入String中的isNotEmpty方法來完成我們的需求(這里以為只判斷是否為空察藐,如果有其他復雜邏輯則不能這么用了皮璧,除非有已經(jīng)定義好的完全匹配的相應方法),如下所示:
    sayHello(testStr2, String::isNotEmpty)
  1. 傳入實現(xiàn)方法類型接口的自定義類對象分飞。這個是什么意思悴务?看下示例就會明白(注意注釋):
//自定義了一個CheckStr類型,實現(xiàn)了 (String) -> Boolean接口
//注意譬猫,這里就不能寫成 (str:String) -> Boolean惨寿,實現(xiàn)方法類型接口時
//不能有命名參數(shù),只能有類型
class CheckStr : (String) -> Boolean {
    override fun invoke(str: String): Boolean = str.isNotEmpty()
}
//調用方法如下
sayHello(testStr2, CheckStr())

至此删窒,上面的例子算是分析完了裂垦。

接下來看下如何通過方法類型的實例來調用方法,示例如下:

fun main(args: Array<String>) {
//定義兩個測試字符串
    val testStr1 = ""
    val testStr2 = "test"
//方法類型實例1:checkStr肌索,接收兩個參數(shù)蕉拢,返回Boolean值。
//實例化的內容是判斷str1以及str2是否為空诚亚,如果都不為
//空則返回true晕换,否則返回false
    val checkStr: (String, String) -> Boolean = { str1, str2 -> str1.isNotEmpty() && str2.isNotEmpty() }
    println(checkStr.invoke(testStr1, testStr2))//調用方式1
    println(checkStr(testStr1, testStr2))//調用方式2
//方法類型實例2:checkStr2,注意這里將上面兩個參數(shù)方法類型
//賦值給了包含有一個receiver站宗、但只有一個入?yún)㈩愋偷姆椒愋?    val checkStr2: String.(String) -> Boolean = checkStr
    println(testStr1.checkStr2(testStr2))//可以直接通過receiver對象調用
}

上面代碼主要闡述一下幾點(原理將會在下面文章中分析):

  1. 方法類型的實例有兩種調用方式闸准,一種是像普通方法調用一樣傳入對應的參數(shù)即可,如checkStr(testStr1, testStr2)梢灭;另一種是通過實例的invoke方法來調用如checkStr.invoke(testStr1, testStr2)夷家。
  2. 對于包含有receiver的方法類型,如果receiver的類型以及該方法類型的剩余參數(shù)類型和沒有顯示定義receiver的方法類的入?yún)㈩愋拖嗥ヅ涿羰停瑒t二者可以進行相互賦值库快。這個理解起來有點費勁,看下示例說明就會明白:如方法類型String.(String) -> Boolean就是顯示包含receiver的方法類型钥顽,該receiver的方法類型是String义屏,參數(shù)類型也是String,所以它就等同于方法類型(String, String) -> Boolean ,該方法類型同時接收兩個String蜂大,而第一個String類型剛好和receiver類型相匹配闽铐,剩下的參數(shù)類型也相互匹配,故可以認為他們是相等的奶浦。

Lambda表達式

Lambda表達式一聽就很神秘的樣子兄墅,其實沒什么,他和匿名方法都被成為“方法字面量”财喳,他們所表達的場景就是察迟,在沒有顯示定義方法的時候斩狱,我們可以通過這種方式生成一個具有同等形式、功能的方法扎瓶。

還是回到文章開頭提到的sayHello這個例子中:

//sayHello方法所踊,第二個參數(shù)類型是方法類型
fun sayHello(str: String, checkStr: (str: String) -> Boolean) {
}
//我們使用中可以這么調用
sayHello("test", { str -> str.isNotEmpty() })
//{ str -> str.isNotEmpty() }就是一個lambda表達式,其作用
//相當于下面顯示定義的checkStr方法
fun checkStr(str: String): Boolean {
    return str.isNotEmpty()
}

代碼中的注釋已經(jīng)闡述了lambda表達式的含義概荷,lambda表達式可以大大簡化代碼的寫法秕岛,也能減少不必要的方法定義,但與此同時帶來的副作用就是是代碼的可讀性大大降低误证。

lambda表達式定義的語法如下所示:

//直接給sum賦值一個方法類型實例继薛,等于后面就是標識的lambda表達式
 val sum = { x: Int, y: Int -> x + y }
//也可以顯示定義sum的類型為(Int,Int)->Int的方法類型
val sum: (Int, Int) -> Int = { x, y -> x + y }

lambda表達式都會被放在{}中,可以指定參數(shù)名稱也可以省略愈捅。當lambda表達式作為方法的最后一個參數(shù)時遏考,可以將其提取到方法外部進行實現(xiàn),如調用上面的sayHello方法可以寫成如下形式:

//直接將lambda表達式提取到了方法體的外部
    sayHello("test") { str -> str.isNotEmpty() }

當lambda表達式是唯一的方法入?yún)r蓝谨,我們可以只保留{}灌具,如下所示:

//假如這里我們定義了只包含一個lambda表達式入?yún)⒌膕ayHello方法
fun sayHello(checkStr: (String) -> Boolean) {
    if (checkStr("test")) {
        println("is not empty...")
    }
}
//那么我們就可以使用及其簡潔的調用方式
sayHello { str -> str.isNotEmpty() }

上面這種就是我們經(jīng)常見到的lambda方法調用形式,剛接觸的時候會一臉懵逼譬巫,理解之后就會發(fā)現(xiàn)咖楣,實際上這種形式只有方法入?yún)⒂星抑挥幸粋€lambda表達式類型的時候的一種簡寫,這也正是lambda表達式的一個吸引人之處芦昔,簡化代碼诱贿。

在kotlin中,對于只有一個lambda入?yún)⒌姆椒ü径校€有一個優(yōu)化的地方珠十,那就是可以使用it來代替實際入?yún)ⅲ热缟厦嬲{用sayHello的方式還可以簡寫為如下代碼:

//it就指代了傳入的str對象
 sayHello { it -> it.isNotEmpty() }

上面代碼中锨阿,我們并沒有顯示的返回lambda的結果宵睦,那么lambda的返回機制是什么呢?照例說話:

//1. 這個是我們常用的調用方法墅诡,并沒有太多關注返回值
    sayHello { it -> it.isNotEmpty() }
//2. 其實我們可以在{}中做更多工作,然后再{}的最后返回我們想要的值
//即lambda會將最后一條語句作為其返回值
    sayHello {
        val result = it.isNotEmpty()
        result
    }
//3. 這里我們顯示指定了返回值桐智,其效果和1末早、2中一樣
//這里使用了精確返回,即return@sayHello说庭,為什么要這樣然磷?
//這是因為如果直接return的話就相當于return當前方法,
//而通過指定label刊驴,就表示要return的是當前的方法塊姿搜。
    sayHello {
        val result = it.isNotEmpty()
        return@sayHello result
    }

上面代碼已經(jīng)注釋的很詳盡寡润,這里不再闡述。

如果一個高階方法接收多個lambda表達式舅柜,當我們不需要傳遞時可以傳入下劃線_梭纹,如下所示:

//我們定義了一個sayHello方法,參數(shù)是一個方法類型致份,
//該方法類型需要兩個String類型的參數(shù)
fun sayHello(checkStr: (String, String) -> Boolean) {
}
//當我們調用sayHello的時候变抽,如果我們不需要傳入?yún)?shù),
//則可以使用_代替氮块,如下代碼意思就是不需要用到第一個入?yún)?sayHello { _, str -> str.isNotEmpty() }

匿名方法

闡述過lambda表達式后绍载,繼續(xù)闡述下匿名方法。相較于lambda表達式滔蝉,匿名方法容易理解多了击儡。

匿名方法同lambda方法同樣都是“方法字面量”,都可以在不顯示定義方法的時候提供具有同樣形式蝠引、功能的實現(xiàn)曙痘。只不過匿名方法更加接近普通方法的定義,如下所示:

//匿名方法的形式如下所示立肘,但并不能這么在文件中定義!
 fun(x: Int, y: Int): Int = x + y

當然匿名方法并不能當而皇之的寫在class中或者top-level中边坤,而只能作為方法實參傳入到方法中,如上面的sayHello可以改用匿名方法的調用形式:

  //通過匿名方法的形式調用sayHello
    sayHello(fun(str: String): Boolean {
        return str.isNotEmpty()
    })
//當方法體只有一條語句時谅年,可以簡化為下面語句進行調用
   sayHello(fun(str: String) = str.isNotEmpty())

匿名方法和lambada表達式的不同茧痒,主要有兩點:

  1. 匿名方法中的入?yún)ⅲ仨氁诺綀A括號里面融蹂。而lambda可以簡化旺订。
  2. 匿名方法返回時,會返回到該方法的調用處超燃,而lambda則會返回最近的方法調用處区拳。

閉包

什么是閉包?很難用一句話來解釋意乓,可以理解為樱调,當一個作用域A位于另一個作用域B中時,A可以訪問到其外部作用域B的相關環(huán)境届良,A和B所構成的環(huán)境就可以稱之為一個閉包笆凌。

kotlin中的lambda表達式和匿名方法都可以訪問其閉包中的相關環(huán)境,這里的相關環(huán)境其實就是指一些變量等士葫。如下所示:

//main為測試方法
fun main(args: Array<String>) {
//main方法中有個變量initVal
    var initVal = 1
//示例1乞而,我們可以在lambda表達式中訪main作用域中的initVal
    sayHello {
        initVal++
        it.isNotEmpty()
    }
//示例2,我們也可以在匿名方法中訪main作用域中的initVal
    sayHello(fun(str: String): Boolean {
        initVal++
        return str.isNotEmpty()
    })
}

至此慢显,高階方法和lambda表達式的用法已闡述完畢爪模。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末欠啤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屋灌,更是在濱河造成了極大的恐慌洁段,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件声滥,死亡現(xiàn)場離奇詭異眉撵,居然都是意外死亡,警方通過查閱死者的電腦和手機落塑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門纽疟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憾赁,你說我怎么就攤上這事污朽。” “怎么了龙考?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵蟆肆,是天一觀的道長。 經(jīng)常有香客問我晦款,道長炎功,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任缓溅,我火速辦了婚禮蛇损,結果婚禮上,老公的妹妹穿的比我還像新娘坛怪。我一直安慰自己淤齐,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布袜匿。 她就那樣靜靜地躺著更啄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪居灯。 梳的紋絲不亂的頭發(fā)上祭务,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音穆壕,去河邊找鬼待牵。 笑死,一個胖子當著我的面吹牛喇勋,可吹牛的內容都是我干的。 我是一名探鬼主播偎行,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼川背,長吁一口氣:“原來是場噩夢啊……” “哼贰拿!你這毒婦竟也來了?” 一聲冷哼從身側響起熄云,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤膨更,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缴允,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荚守,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年练般,在試婚紗的時候發(fā)現(xiàn)自己被綠了矗漾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡薄料,死狀恐怖敞贡,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情摄职,我是刑警寧澤誊役,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谷市,受9級特大地震影響蛔垢,放射性物質發(fā)生泄漏。R本人自食惡果不足惜迫悠,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一鹏漆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧及皂,春花似錦甫男、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碍拆,卻和暖如春若治,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背感混。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工端幼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弧满。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓婆跑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庭呜。 傳聞我的和親對象是個殘疾皇子滑进,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353