Kotlin系列 - 高階函數(shù)與標準庫中的常用函數(shù)(三)

Kotlin細節(jié)文章筆記整理更新進度:
Kotlin系列 - 基礎(chǔ)類型結(jié)構(gòu)細節(jié)小結(jié)(一)
Kotlin系列 - 函數(shù)與類相關(guān)細節(jié)小結(jié)(二)

1.高階函數(shù)

基本概念: 傳入或者返回函數(shù)的函數(shù)
函數(shù)引用:引用的函數(shù)名前加上 ::

  • 有以下幾種類型:
  • 類成員方法引用:類名::成員方法名
  • 擴展函數(shù)引用:類名::擴展函數(shù)名
  • 實例函數(shù)引用:實例名::成員方法名
  • 包級別函數(shù)引用:::函數(shù)名

第一個例子:

打印數(shù)組中的元素(傳入包級別函數(shù))

fun main(args:Array<String>) {
        args.forEach(::println) //函數(shù)引用
}

public actual inline fun println(message: Any?) {
    System.out.println(message)
}

public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

forEach(action: (T) -> Unit):要求傳入了一個函數(shù)廉涕,參數(shù)為(action: (T) -> Unit),類型為一個參數(shù)T,返回值為Unit
println(message: Any?):類型為一個參數(shù)T,返回值為Unit
我們調(diào)用args.forEach(::println)println函數(shù)傳入給forEach

第二個例子:

過濾數(shù)組中的空字符串(傳入類成員函數(shù))

fun main(args:Array<String>) {
    args.filter(String::isNotEmpty)
}

public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun CharSequence.isNotEmpty(): Boolean = length > 0

這里有個點要注意下: filter要求傳入的函數(shù)類型為(predicate: (T) -> Boolean),但是我們傳入的String::isNotEmpty這個方法并沒有參數(shù)0妗X舶琛具则!public inline fun CharSequence.isNotEmpty(): Boolean = length > 0 只有一個返回值Boolean為什么可以呢苛骨??顾稀?

答案:因為類名::成員方法名默認就有一個參數(shù)达罗,這個函數(shù)類型就是類名這個類型的。比如上面的String::isNotEmpty相當于isNotEmpty(String)

第三個例子:

打印數(shù)組中的元素(傳入實例函數(shù))

fun main(args:Array<String>) {
    val t = Test()
    args.forEach(t::testName)
}
class Test{
    fun testName(name:String){
        println(name)
    }
}
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

這里傳入的是t::testName,實例名::成員方法名就不會默認多出一個參數(shù)静秆。如果使用Test::testName會顯示報錯信息粮揉,也驗證了我們上面說的類名::成員方法名默認就有一個參數(shù)。

image.png

這里總結(jié)一下:函數(shù)引用抚笔,就是將函數(shù)作為參數(shù)變量傳入具體某個方法中扶认,也可以賦值給變量。注意的是殊橙,如果是類成員函數(shù)辐宾、擴展函數(shù)引用(類名:函數(shù)名),默認參數(shù)會多一個就是類本身這個參數(shù)

2. 閉包

  • 函數(shù)運行的環(huán)境
  • 持有函數(shù)運行狀態(tài)
  • 函數(shù)內(nèi)部可以定義函數(shù)/類
fun add(x: Int): (Int) -> Int {
    return fun(y: Int): Int {
        return x + y
    }
}
fun main() {
    var add2 = add(2)
    println(add2(10))
}

函數(shù)的定義方法可以傳入函數(shù)蛀柴,也可以返回函數(shù)螃概,函數(shù)內(nèi)的作用域包含了函數(shù)內(nèi)的子函數(shù)跟子類等。
格式 : fun 方法名(形參:函數(shù)類型) 函數(shù)類型{}
函數(shù)類型基本寫法:
() -> Unit
(多個參數(shù)) -> 返回類型

3. 函數(shù)復(fù)合

  • f(g(x)) 函數(shù)傳入函數(shù)
//定義兩個函數(shù)
val add5 = { i: Int -> i + 5 }

val multiplyBy2 = { i: Int -> i * 2 }

fun main() {
    println(multiplyBy2(add5(9)))
}
-----打印出來的Log
28

上面是基本的展示鸽疾,函數(shù)中傳入函數(shù)吊洼。
下面擴展一下函數(shù):

//定義三個函數(shù)
val add5 = { i: Int -> i + 5 }
val multiplyBy2 = { i: Int -> i * 2 }
val sum = { q: Int, w: Int -> q + w }
//關(guān)鍵點1:
infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p1: P1): R {
        return function.invoke(this.invoke(p1))
    }
}

// 關(guān)鍵點2:
infix fun <P1,P2,R> Function1<P2,R>.compose(function:Function1<P1,P2>):Function1<P1,R>{
    return fun (p1:P1):R{
        return this.invoke(function.invoke(p1))
    }
}
//關(guān)鍵點3:
fun <P1, P2, P3, R> Function2<P2, P3, R>.toAllSum(
    function: Function1<P1, P2>,
    function1: Function1<P2, P3>
): Function2<P1, P2, R> {
    return fun(p1: P1, p2: P2): R {
        return this.invoke(function.invoke(p1), function1.invoke(p2))
    }
}

fun main() {
    // 關(guān)鍵點3:
    val add5AndMulti2 = add5 andThen multiplyBy2
    // 關(guān)鍵點4:
    val add5ComposeMulti2 = add5 compose multiplyBy2
    //關(guān)鍵點5:
    val sum = sum.toAllSum(add5, multiplyBy2)

    println(add5AndMulti2(10))
    println(add5ComposeMulti2(10))
    println(sum(10,10))
}

-----打印出來的Log
30
25
35

上面實際上就是擴展函數(shù),然后在函數(shù)中傳入函數(shù)跟返回函數(shù)制肮,只有一個參數(shù)的則使用了infix中綴關(guān)鍵字冒窍。

關(guān)鍵點1、2豺鼻、3都是擴展了函數(shù)類型综液,其中關(guān)鍵點1跟2 擴展函數(shù)類型為傳入一個函數(shù)參數(shù),關(guān)鍵點3擴展函數(shù)傳入兩個函數(shù)參數(shù)

舉例:關(guān)鍵點1:函數(shù)類型為Function<P1,P2>擴展函數(shù)andThen,傳入函數(shù)類型Function1<P2, R>儒飒,返回函數(shù)類型Function1<P1, R>
第一個return:返回函數(shù)類型為fun(p1: P1): R
第二個return:function.invoke(this.invoke(p1))谬莹,先是傳入this.invoke(p1)再將這里返回的值傳入
function.invoke().
先調(diào)用了了andThen前的函數(shù),再調(diào)用andThen后面的函數(shù)。
大家可以根據(jù)這些寫法自定義多種擴展函數(shù)~~

4. run附帽、let埠戳、with、apply蕉扮、also等語法糖部分解析

  • 先以run為例子
//方法一
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
//方法二
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

方法一函數(shù)簽名:run(block: () -> R): R直接傳入代碼塊整胃,并返回R
方法二函數(shù)簽名:T.run(block: T.() -> R): R,傳入的代碼塊block: T.() -> R喳钟,也就是調(diào)用者本身的引用屁使,則在block中則直接可以使用T中的成員變量及函數(shù)等

//使用
var sum = run { 5+3 }
println(sum)

var aList = arrayListOf("小明", "小紅", "小黑")
var aListSize = aList.run { size }
println(aListSize)
--------------------打印出來的
8
3

這個 contract {...}看不懂可以暫時不用管它,kotlin中契約的一種寫法,詳情可以看一下https://kotlinlang.org/docs/reference/whatsnew13.html#contracts

  • with奔则、apply蛮寂、alsolet
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
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
}
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
  • with:接受兩個參數(shù)应狱,一個是自己本身共郭,一個block: T.() -> R,返回return receiver.block()
    with用法:
var aList = arrayListOf("小明", "小紅", "小黑")
var l = with(aList){
  add("小黃")
  removeAt(0)
  forEach {
    print("$it疾呻、")
  }
  size
}
println(l)
---------------------打印
小紅除嘹、小黑、小黃岸蜗、3
  • apply:方法的擴展函數(shù)尉咕,傳入block: T.() -> Unit,返回調(diào)用者本身璃岳,用法與run一致年缎,但是最后返回的是調(diào)用者本身。
  • also:方法的擴展函數(shù)铃慷,傳入block: (T) -> Unit单芜,這里更前面幾個方法有點不一樣,block傳入了T這個調(diào)用者本身犁柜,并且函數(shù)最后返回調(diào)用者本身洲鸠。
    also用法:
var aList = arrayListOf("小明", "小紅", "小黑")
val  sizeFinally = aList.also {
  println(it.size)
  it.add("小黃")
  it.add("小綠")
}.size
println(sizeFinally)
---------打印
3
5
  • let:方法的擴張函數(shù),傳入block: (T) -> R馋缅,let方法返回R扒腕。
    let用法:
val  sizeFinally = aList.let {
        println(it.size)
        it.add("小黃")
        it.add("小綠")
        it.size
}
  println(sizeFinally)
---------------打印
3
5

補充: 尾遞歸優(yōu)化 tailrec

  • tailrec關(guān)鍵字添加到fun前提示編譯器尾遞歸優(yōu)化。
    尾遞歸:是遞歸的一種形式萤悴,遞歸中在調(diào)用完自己后沒有其他操作的稱為尾遞歸瘾腰。
  • 尾遞歸與迭代的關(guān)系:尾遞歸可以直接轉(zhuǎn)換成迭代(好吧,其實這個我也不是很清楚~)
//符合尾遞歸 可以加tailrec 關(guān)鍵字優(yōu)化
tailrec fun findListNode(head: ListNode?, value: Int): ListNode? {
    head ?: return null
    if (head.value == value) return head
    return findListNode(head.next, value)
}
// 不符合尾遞歸 因為最后調(diào)用完自己還跟n相乘 
fun factorial(n:Long):Long{
  return n * factorial(n-1)
}

上面的方法中第一種是符合尾遞歸的形式覆履,這種我們可以加tailrec關(guān)鍵字蹋盆,有什么好處呢费薄?

fun main() {
    var listNode = ListNode(0)
    var p =listNode
    for (i in 1..100000) {
        p.next = ListNode(i)
        p = p.next!!
    }
   println(findListNode(listNode,99998)?.value)
}
-----------有加了關(guān)鍵字tailrec -打印出來的Log
99998
----------沒有加關(guān)鍵字打印出來的Log
Exception in thread "main" java.lang.StackOverflowError

因為加了tailrec關(guān)鍵字,實際上是優(yōu)化成了迭代相比遞歸減低了內(nèi)存空間的開銷栖雾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末义锥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岩灭,更是在濱河造成了極大的恐慌,老刑警劉巖赂鲤,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件噪径,死亡現(xiàn)場離奇詭異,居然都是意外死亡数初,警方通過查閱死者的電腦和手機找爱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泡孩,“玉大人车摄,你說我怎么就攤上這事÷嘏福” “怎么了吮播?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長眼俊。 經(jīng)常有香客問我意狠,道長,這世上最難降的妖魔是什么疮胖? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任环戈,我火速辦了婚禮,結(jié)果婚禮上澎灸,老公的妹妹穿的比我還像新娘院塞。我一直安慰自己,他們只是感情好性昭,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布拦止。 她就那樣靜靜地躺著,像睡著了一般巩梢。 火紅的嫁衣襯著肌膚如雪创泄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天括蝠,我揣著相機與錄音鞠抑,去河邊找鬼。 笑死忌警,一個胖子當著我的面吹牛搁拙,可吹牛的內(nèi)容都是我干的秒梳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼箕速,長吁一口氣:“原來是場噩夢啊……” “哼酪碘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盐茎,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兴垦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后字柠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體探越,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年窑业,在試婚紗的時候發(fā)現(xiàn)自己被綠了钦幔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡常柄,死狀恐怖鲤氢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情西潘,我是刑警寧澤卷玉,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站喷市,受9級特大地震影響揍庄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜东抹,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一蚂子、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缭黔,春花似錦食茎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惧互,卻和暖如春哎媚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喊儡。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工拨与, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人艾猜。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓买喧,卻偏偏與公主長得像捻悯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子淤毛,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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

  • 如果使用AppCompat包
    超威藍貓l閱讀 267評論 0 1
  • 兒童節(jié)今缚,試著調(diào)整到兒童的狀態(tài)。 好奇低淡、純凈姓言、想到什么就實現(xiàn)什么,活在當下蔗蹋。簡單事期。 發(fā)現(xiàn)很難,但很美纸颜。 過節(jié),現(xiàn)在有...
    雄安區(qū)閱讀 136評論 0 1
  • 過日子可像近視一樣绎橘,在路上走時把行人或者有幾面之緣胁孙,認識的人自動忽略掉直往前走就是了,不要瞻前顧后老想著給人家就...
    垃圾桶里裝著悲傷閱讀 59評論 0 1
  • 戈叔: 今天你說想出去走走称鳞,我突然想起了之前一起旅行的事情涮较。 我們真是一對奇怪的人啊,老挑著“不合適”的日子去旅行...
    于阿心閱讀 255評論 0 2