前言
高階函數(shù)系列文章:
Kotlin 高階函數(shù)從未如此清晰(上)
Kotlin 高階函數(shù)從未如此清晰(中)
Kotlin 高階函數(shù)從未如此清晰(下) let/also/with/run/apply/repeat 一看就會
上一篇羅列過Kotlin的屬性與函數(shù)的基本知識,算是入門篇本画畅。本篇將繼續(xù)對函數(shù)的一些高級用法進(jìn)行深入分析挺尾。
通過本篇注盈,你將了解到:
1牲距、什么是函數(shù)類型?
2菱皆、Kotlin 函數(shù)類型形參聲明/實(shí)參定義
3恤浪、Kotlin 函數(shù)類型參數(shù)調(diào)用
4、匿名函數(shù)與Lambda
5、Kotlin 函數(shù)作為返回值
6严拒、Java 如何調(diào)用Kotlin 函數(shù)扬绪?
1、什么是函數(shù)類型裤唠?
Java 如何傳遞方法挤牛?
有個(gè)場景:
輸入學(xué)生的姓名、年齡种蘸,返回該學(xué)生的考試分?jǐn)?shù)墓赴。
通常我們會將它封裝為一個(gè)方法,而方法需要放在類或者接口里航瞭,最終調(diào)用時(shí)是通過類/接口的實(shí)例化對象調(diào)用該方法竣蹦,如下:
private void testStudent(HandleStudent handleStudent) {
float score = 0;
if (handleStudent != null) {
score = handleStudent.getScore("fish", 18);
}
System.out.println("score:" + score);
}
//接口
interface HandleStudent {
//傳入學(xué)生的姓名、年齡沧奴,返回學(xué)生的分?jǐn)?shù)
float getScore(String name, int age);
}
實(shí)際上我們只需要調(diào)用getScore(xx)方法痘括,為了實(shí)現(xiàn)這個(gè)目的,需要將它放到類/接口封裝滔吠,最后生成實(shí)例對象調(diào)用纲菌,多了好幾個(gè)步驟。
有沒有更簡單的方式呢疮绷?比如直接傳遞方法本身翰舌?
答案是:沒有。
因?yàn)樵贘ava 的世界里冬骚,類/接口 是一等公民椅贱,方法必須依賴于它們存在。
Kotlin 函數(shù)類型
Java 不支持方法作為方法的參數(shù)只冻,而Kotlin 卻支持函數(shù)作為函數(shù)的參數(shù)/返回值庇麦。
因?yàn)樵贙otlin 的世界里,函數(shù)是一等公民喜德,可以脫離類/接口而存在山橄。
如果你接觸過C++等語言,相信你對函數(shù)參數(shù)不會太陌生舍悯,C++里有函數(shù)指針航棱,指向的是一個(gè)函數(shù)的指針,通過該指針就可以調(diào)用其指向的函數(shù)。
還是以獲取學(xué)生分?jǐn)?shù)為例:
fun upFun1(name: String, age: Int): Float {
return 88f
}
我們只關(guān)注該函數(shù)的輸入?yún)?shù)與返回值,并不關(guān)心該函數(shù)的名字岸更,而函數(shù)的輸入?yún)?shù)與返回值就決定了該函數(shù)的類型。
upFun1 的函數(shù)類型為:
(String, Int)->Float
輸入?yún)?shù)類型為:String 和 Int朴艰,多個(gè)參數(shù)之間用","隔開,所有參數(shù)使用()括起來
返回值類型為:Float,返回值與輸入?yún)?shù)之間使用"->"連接呵晚。
如此一來就可以表示一個(gè)函數(shù)的類型蜘腌。
2、Kotlin 函數(shù)類型形參聲明/實(shí)參定義
形參聲明
在上一篇文章里饵隙,我們有提到過:Kotlin里的引用類型包括函數(shù)這種引用類型撮珠,既然是引用,那么當(dāng)然可以作為參數(shù)傳遞了金矛,來看看如何聲明一個(gè)使用了函數(shù)作為形參的函數(shù)芯急。
//testUpFun1 接收的參數(shù)為函數(shù)類型:(String, Int)->Float
fun testUpFun1(getScore : (String, Int)->Float) {
}
傳入的形參不使用的話沒啥意義,對于函數(shù)類型驶俊,通常是調(diào)用該函數(shù)娶耍,如下:
//testUpFun1 接收的參數(shù)為函數(shù)類型:(String, Int)->String
fun testUpFun1(getScore : (String, Int)->Float) {
var score = getScore("fish", 18)
println("student score:$score")
}
實(shí)參定義
形參有了,當(dāng)調(diào)用testUpFun1(xx)時(shí)需要傳入實(shí)參饼酿,也就是傳入函數(shù)的定義:
fun upFun1(name: String, age: Int): Float {
return 88f
}
定義了upFun1函數(shù)榕酒,該函數(shù)類型為:(String, Int)->Float,符合作為testUpFun1 形參的條件故俐。
3想鹰、Kotlin 函數(shù)類型參數(shù)調(diào)用
形參和實(shí)參都有了,接著來看如何將兩者結(jié)合起來药版,總結(jié)來說有如下幾種方式:
接下來一一看看三者的實(shí)現(xiàn)方式辑舷。
函數(shù)引用
當(dāng)我們定義了一個(gè)函數(shù)后,想將這個(gè)函數(shù)作為實(shí)參傳遞給另一個(gè)函數(shù)槽片,可以通過:: + 函數(shù)名 的方式傳遞何缓,官方說法叫做:函數(shù)引用。
fun upFun1(name: String, age: Int): Float {
return 88f
}
//testUpFun1 接收的參數(shù)為函數(shù)類型:(String, Int)->String
fun testUpFun1(getScore : (String, Int)->Float) {
var score = getScore("fish", 18)
println("student score:$score")
}
fun main(args: Array<String>) {
//通過函數(shù)引用調(diào)用
testUpFun1(::upFun1)
}
如上还栓,testUpFun1 函數(shù)需要傳入一個(gè)函數(shù)類型的參數(shù)碌廓,通過"::"引用函數(shù)名即可。
變量(函數(shù)類型)
普通函數(shù)定義
既然函數(shù)引用可以當(dāng)做參數(shù)傳遞蝙云,那么它當(dāng)然可以賦值給變量氓皱,如下:
fun upFun1(name: String, age: Int): Float {
return 88f
}
//賦值
//var varFun:(String, Int)->Float = ::upFun1
//類型推斷,可以不用寫變量類型
var varFun = ::upFun1
fun main(args: Array<String>) {
testUpFun1(varFun)
}
此時(shí)勃刨,我們只需要把varFun 作為實(shí)參傳遞即可。
需要注意的是:Kotlin 會對變量進(jìn)行類型推斷股淡,因此我們可以省略變量類型
匿名函數(shù)
當(dāng)然了身隐,若是想要在聲明變量的同時(shí)將函數(shù)定義了,這也是可以的:
//匿名函數(shù)
//var varFun1:(String, Int)->Float = fun (name: String, age: Int):Float {
// return 88f
//}
//類型推斷
var varFun1 = fun (name: String, age: Int):Float {
return 88f
}
fun main(args: Array<String>) {
testUpFun1(varFun1)
}
可以看出唯灵,我們聲明的函數(shù)沒有函數(shù)名贾铝,只有一個(gè)"fun"聲明。
此時(shí),varFun1 表示的是一個(gè)匿名函數(shù)垢揩。
同樣的因?yàn)轭愋妥詣油茖?dǎo)玖绿,可以不用寫變量類型。
Lambda 表達(dá)式
在Java 里叁巨,有時(shí)候我們會將匿名內(nèi)部類轉(zhuǎn)為Lambda形式斑匪,而Kotlin 對于Lambda的使用更廣泛了,上面的匿名函數(shù)我們可以用Lambda表示锋勺。
//Lambda 表達(dá)式
//var lambda1:(String, Int)->Float = {
// name:String,age:Int->
// 88f
//}
//類型推導(dǎo)
var lambda1 = { name: String, age: Int ->
88f
}
fun main(args: Array<String>) {
testUpFun1(lambda1)
}
可以看出蚀瘸,變量作為實(shí)參傳遞,對比普通函數(shù)定義庶橱、匿名函數(shù)贮勃、Lambda 表達(dá)式三者寫法,發(fā)現(xiàn)Lambda最簡潔苏章,簡潔有時(shí)候也意味著難以理解寂嘉。
Lambda 格式以及一些風(fēng)騷寫法,我們放在下節(jié)分析
直接傳入函數(shù)體
不管是函數(shù)引用還是變量作為實(shí)參枫绅,都需要先將函數(shù)定義好泉孩,有時(shí)候函數(shù)只在一個(gè)地方使用,無需再單獨(dú)定義出來撑瞧,此時(shí)可以選擇直接將函數(shù)體當(dāng)做實(shí)參傳遞棵譬。
匿名函數(shù)
在調(diào)用函數(shù)的時(shí)候,直接傳入匿名函數(shù)作實(shí)參:
//testUpFun1 接收的參數(shù)為函數(shù)類型:(String, Int)->String
fun testUpFun1(getScore: (String, Int) -> Float) {
var score = getScore("fish", 18)
println("student score:$score")
}
fun main(args: Array<String>) {
//傳入匿名函數(shù)
testUpFun1(fun(name: String, age: Int): Float {
return 88f
})
}
Lambda
老樣子预伺,一般匿名函數(shù)都可以用Lambda 替代:
fun main(args: Array<String>) {
//Lambda 表示
testUpFun1({ name: String, age: Int ->
88f
}
)
}
此時(shí)订咸,編譯器會提示你可以再優(yōu)化一下寫法:
fun main(args: Array<String>) {
//傳入匿名函數(shù)
testUpFun1 { name: String, age: Int ->
88f
}
}
我們知道函數(shù)的調(diào)用需要用"()"括起來,此時(shí)"()"都沒了酬诀,越來越簡潔了脏嚷。
4、匿名函數(shù)與Lambda
上面簡單展示了匿名函數(shù)和Lambda的使用瞒御,只是一些基本寫法父叙,尤其是Lambda還有一些風(fēng)騷寫法,接著來分析肴裙。
匿名函數(shù)
顧名思義趾唱,函數(shù)是有函數(shù)名的,如果省略了函數(shù)名那么就稱之為匿名函數(shù)蜻懦。
//定義匿名函數(shù)
var anoymous1: (String, Int) -> Float = fun(name: String, age: Int): Float {
println("name:$name age:$age")
return 88f
}
//自動推導(dǎo)甜癞,消除變量類型
var anoymous2 = fun(name: String, age: Int): Float {
println("name:$name age:$age")
return 88f
}
//調(diào)用
fun main(args: Array<String>) {
//傳入匿名函數(shù)
testUpFun1(fun(name: String, age: Int): Float {
println("name:$name age:$age")
return 88f
})
testUpFun1(anoymous1)
testUpFun1(anoymous2)
}
需要注意的是:
匿名函數(shù)返回值表示的是該匿名函數(shù)本身的返回值。
Lambda
匿名函數(shù)還是不夠簡潔宛乃,此時(shí)Lambda出現(xiàn)了悠咱,分三步闡述蒸辆。
Lambda 基本結(jié)構(gòu)
var varLambda2 = { name: String, int: Int ->
println()
test()
"jj"
}
以此為例,Lambda 有如下約定:
1析既、大括號"{}" 包裹內(nèi)容躬贡。
2、使用"->"連接參數(shù)與實(shí)現(xiàn)體眼坏。
3拂玻、"->"左邊表示參數(shù)列表,參數(shù)間使用","分割空骚。
4纺讲、"->"右邊表示實(shí)現(xiàn)體,多個(gè)語句分行表示囤屹。
5熬甚、如果沒有參數(shù)列表,那么"->"可以省略
6肋坚、Lambda 無需"return"關(guān)鍵字乡括,最后一行默認(rèn)表示返回值(如例子中"jj"表示Lambda返回了String類型
變量接收Lambda
//完整寫法
var varLambda1:(String, Int)->Float = { name: String, age: Int ->
println("student name:$name age:$age")
88f
}
因?yàn)樽詣油茖?dǎo)類型,因此可以省略變量類型:
//省略類型
var varLambda1 = { name: String, age: Int ->
println("student name:$name age:$age")
88f
}
當(dāng)然智厌,非得要類型的話诲泌,還可以這么寫:
//Lambda里省略了參數(shù)類型,因?yàn)?="之前已經(jīng)聲明了
var varLambda1: (String, Int) -> Float = { name, age ->
println("student name:$name age:$age")
88f
}
函數(shù)調(diào)用傳入Lambda
接下來通過不同的case由淺入深演示Lambda各種風(fēng)騷寫法铣鹏。
第一種Case:
將之前的testUpFun1 改造一下敷扫,新增一個(gè)參數(shù),如下:
//testUpFun1 接收的參數(shù)為函數(shù)類型:(String, Int)->String
fun testUpFun1(getScore: (String, Int) -> Float) {
var score = getScore("fish", 18)
println("student score:$score")
}
//改造后
fun testUpFun2(getScore: (String, Int) -> Float, needDelay:Boolean) {
if (needDelay)
println("delay...")
var score = getScore("fish", 18)
println("student score:$score")
}
testUpFun2 函數(shù)有兩個(gè)參數(shù)诚卸,一個(gè)是函數(shù)類型葵第,另一個(gè)是Boolean。
接著來看看如何調(diào)用testUpFun2 函數(shù)合溺,我們以直接傳入函數(shù)體為例:
fun main2(args : Array<String>) {
testUpFun2({ name: String, age: Int ->
println("name:$name age:$age")
88f
}, true
)
}
第二種Case:
我們再變一下testUpFun2 參數(shù)卒密,函數(shù)類型和Boolean交換位置:
fun testUpFun3( needDelay: Boolean, getScore: (String, Int) -> Float) {
if (needDelay)
println("delay...")
var score = getScore("fish", 18)
println("student score:$score")
}
調(diào)用如下:
fun main3(args: Array<String>) {
testUpFun3(true, { name: String, age: Int ->
println("name:$name age:$age")
88f
}
)
}
此時(shí),編譯器會提示你可以將"{}"整體提取出來放在"()"括號后棠赛,如下:
fun main3(args: Array<String>) {
testUpFun3(true
) { name: String, age: Int ->
println("name:$name age:$age")
88f
}
}
這是Lambda的一個(gè)約定:
如果Lambda 作為函數(shù)的最后一個(gè)參數(shù)哮奇,那么Lambda可以提取到"()"外展示。
第三種Case:
再對testUpFun3 參數(shù)做調(diào)整睛约,只保留一個(gè)函數(shù)類型的參數(shù):
//定義
fun testUpFun4(getScore: (String, Int) -> Float) {
var score = getScore("fish", 18)
println("student score:$score")
}
//調(diào)用
fun main4(args: Array<String>) {
testUpFun4(
) { name: String, age: Int ->
println("name:$name age:$age")
88f
}
}
同樣的編譯器會提示可以將"()"省略鼎俘,如下:
fun main4(args: Array<String>) {
//省略"()"
testUpFun4 { name: String, age: Int ->
println("name:$name age:$age")
88f
}
}
這是Lambda的一個(gè)約定:
如果Lambda 作為函數(shù)的唯一參數(shù),那么調(diào)用函數(shù)"()"可以省略辩涝。
第四種Case:
這次不修改testUpFunX而芥,我們修改Lambda表達(dá)式的入?yún)ⅲ臑椋?/p>
//單參數(shù)
fun testUpFun5(getScore: (String) -> Float) {
var score = getScore("fish")
println("student score:$score")
}
調(diào)用如下:
fun main5(args: Array<String>) {
////省略"()"
testUpFun5 { name: String ->
println("name:$name")
88f
}
}
此時(shí)膀值,可以寫成如下方式:
fun main5(args: Array<String>) {
////省略"()"
testUpFun5 {
//用it 替代了Lambda 的name
println("name:$it")
88f
}
}
這是Lambda的一個(gè)約定:
如果Lambda 入?yún)⒅挥幸粋€(gè)棍丐,那么可以省略"->"以及入?yún)⒘斜恚⒃趯?shí)現(xiàn)體里用it 指代這個(gè)唯一的參數(shù)沧踏。
第五種Case:
Lambda 有1個(gè)入?yún)⒖梢杂?it"指代歌逢,Lambda 沒有入?yún)⒛兀?/p>
//無參數(shù)
fun testUpFun6(getScore: () -> Float) {
var score = getScore()
println("student score:$score")
}
fun main6(args: Array<String>) {
////省略"()"
testUpFun6 {
println("name")
88f
}
}
可以看出,此時(shí)只需要"{}"括起來即可翘狱。
這是Lambda的一個(gè)約定:
如果Lambda 沒有入?yún)⒚匕福敲纯梢允÷?->"以及入?yún)⒘斜怼?/p>
注:此時(shí)在Lambda里不能使用"it",因?yàn)樗緵]入?yún)ⅰ?/em>
以上就是Kotlin Lambda 常用的一些變換規(guī)則潦匈。
5阱高、Kotlin 函數(shù)作為返回值
定義函數(shù):
fun testUpFun7(getScore: (String) -> Unit): (Boolean, Int) -> String {
//調(diào)用函數(shù)
var score = getScore("fish")
println("student score:$score")
//返回函數(shù),Lambda表示
return { need: Boolean, age: Int ->
println("need:$need age:$age")
"fish"
}
}
調(diào)用:
fun main7(args: Array<String>) {
////省略"()"
var testReturn = testUpFun7 {
println("name:$it")
}
//調(diào)用
testReturn(true, 5)
}
只要掌握了高階函數(shù)的傳參茬缩,返回值也不在話下赤惊,此處就不展開細(xì)說了。
6凰锡、Java 如何調(diào)用Kotlin 函數(shù)未舟?
以上都是Kotlin 調(diào)用 Kotlin,來看Java 如何調(diào)用Kotlin的高階函數(shù)掂为。
還是以如下函數(shù)為例:
fun testUpFun3(needDelay: Boolean, getScore: (String, Int) -> Float) {
if (needDelay)
println("delay...")
var score = getScore("fish", 18)
println("student score:$score")
}
在Java里調(diào)用:
private void testKotlin() {
UpFunKt.testUpFun3(true, new Function2<String, Integer, Float>() {
@Override
public Float invoke(String s, Integer integer) {
return null;
}
});
}
可以看出testUpFun3里的函數(shù)類型參數(shù)轉(zhuǎn)化為了Function2 的實(shí)例裕膀,F(xiàn)unction2 為何方神圣?
實(shí)際上就是Kotlin 里為了兼容Java 調(diào)用定義了一堆接口勇哗,這些接口標(biāo)明了入?yún)⒑头祷刂抵缈福琂ava 調(diào)用時(shí)需要重寫invoke()方法即可,當(dāng)在Kotlin里調(diào)用對應(yīng)的函數(shù)參數(shù)時(shí)欲诺,將會調(diào)用到invoke()回到Java 代碼抄谐。
在Functions.kt里定義了23個(gè)接口:
基本上可以滿足大部分的參數(shù)需求。
小結(jié)
理解了以上內(nèi)容瞧栗,我相信大家對Lambda各種寫法都不會再陌生斯稳,如果你還是有疑惑,可能是我沒闡述明白迹恐,歡迎留言討論挣惰。
下篇將會繼續(xù)分析泛型函數(shù)、擴(kuò)展函數(shù)殴边、內(nèi)聯(lián)函數(shù)憎茂、常用的高階函數(shù)如let/run/apply 等,進(jìn)而自然過渡到協(xié)程的分析锤岸,那時(shí)再看協(xié)程就事半功倍了竖幔。
本文基于Kotlin 1.5.3,文中Demo請點(diǎn)擊
您若喜歡是偷,請點(diǎn)贊拳氢、關(guān)注募逞,您的鼓勵(lì)是我前進(jìn)的動力
持續(xù)更新中,和我一起步步為營系統(tǒng)馋评、深入學(xué)習(xí)Android/Kotlin
1放接、Android各種Context的前世今生
2、Android DecorView 必知必會
3留特、Window/WindowManager 不可不知之事
4纠脾、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6蜕青、Android invalidate/postInvalidate/requestLayout 徹底厘清
7苟蹈、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9右核、Android 鍵盤一招搞定
10慧脱、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12蒙兰、Android Activity創(chuàng)建到View的顯示過
13磷瘤、Android IPC 系列
14、Android 存儲系列
15搜变、Java 并發(fā)系列不再疑惑
16采缚、Java 線程池系列
17、Android Jetpack 前置基礎(chǔ)系列
18挠他、Android Jetpack 易懂易學(xué)系列