Kotlin的高階函數和常用高階函數

高階函數的定義

將函數當做參數或者是返回值的函數

什麼是高階函數

可以看看我們常用的 forEach 函數:

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

首先我們可以知道, forEachArray 的擴展函數,然後參數是 action ,但是 action 不再像和我們以前Java那樣傳遞的是一個對象,這時傳遞的是一個函數 。這個函數的入參爲 T ,返回值爲 Unit 。所以 forEach 也是一個高階函數,因爲它將函數當做參數進行傳遞了。我們嘗試着去調用一下 forEach 函數:

1fun main(args: Array<String>) {
2    args.forEach(::println)
3}

調用的時候,我們將 println 函數傳遞給了 forEach 函數,這裏採用的是函數引用 。就上訴代碼,我們還可以結合 Lambda 表達式來進行處理:

 1fun main(args: Array<String>) {
 2    args.forEach ({
 3        println(it)
 4    })
 5    args.forEach{
 6        println(it)
 7    }
 8    args.forEach(){
 9        println(it)
10    }
11}

其實以上幾種的方式得到的結果都是一樣的,但是第一種就是簡潔了許多。

我們再定義一個類,用來打印 forEach 的值:

1class PdfPrinter {
2    fun println(any: Any) {
3        kotlin.io.println(any)
4    }
5}

根據函數引用的特性,我們可以這樣調用 println(any: Any) 函數:PdfPrinter::println 。由於 PdfPrinter 中的 println 函數的入參類型是 Any 類型,也就是任意類型,不管 forEach 傳遞的是什麼值都可以接收。那現在我們再將其作爲 forEach 的參數傳遞進去:

編譯器告訴我們這個是錯誤的。那我們來分析一下吧:我們再定義一個 Hello

1class Hello {
2    fun world() {
3        println("Hello World.")
4    }
5}

然後進行以下操作:

可以看到 Android Studio 分別對 helloWorldprinter 的解釋:

  • helloWorld 是一個方法,然後參數類型爲 Hello ,返回值爲 Unit
  • printer 也是一個方法,但是參數有兩個,分別是 PdfPrinterAny 類型, 返回值爲 Unit

forEach 中,只有一個參數傳遞,但是 PdfPrinter::println 需要的是兩個參數,肯定就會報錯,所以我們需要對此進行修改:

1fun main(args: Array<String>) {
2    var pdfPrinter = PdfPrinter()
3    args.forEach(pdfPrinter::println)
4}

這個樣子就OK了。

常用高階函數

map:變換

通常我們會使用以下的方式來實現對集合中的元素進行修改的操作:

 1fun main(args: Array<String>) {
 2    var list = listOf(1, 3, 4, 7, 89)
 3    var newList = mutableListOf<Int>()
 4    list.forEach {
 5        var element = it * 2 + 1
 6        newList.add(element)
 7    }
 8    newList.forEach(::println)
 9}
10打印結果:
113
127
139
1415
15179

如果採用這種方式,遠遠不能體現Kotlin的優勢了,這個和Java有什麼區別呢?「狗子,上map」:

 1fun main(args: Array<String>) {
 2    var list = listOf(1, 3, 4, 7, 89)
 3    var newList = list.map {
 4        it * 2 + 1
 5    }
 6    newList.forEach(::println)
 7}
 8打印結果:
 93
107
119
1215
13179

從打印結果可以看到他們的實現效果是一模一樣的,這個就是 map 的功能,可以對集合中的元素進行你想要的操作,是不是跟 RxJavamap 很類似呢!我們來細看一下map 的實現原理:

 1@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
 2public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
 3    // 新創建一個ArrayList,默認長度是10,將ArrayList跟transform傳遞給mapTo
 4    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
 5}
 6
 7public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
 8    // 進行迭代遍歷
 9    for (item in this)
10    // 將進行過變化的數據添加到新的集合中
11        destination.add(transform(item))
12    return destination
13}

map 方法中主要做的就是調用 mapTo 方法,然後傳遞的是新創建並且初始長度爲10的 ArrayListtransform 函數,在 mapTo 方法中,對集合進行迭代,然後將進行變換後的數據添加到新的集合中,最後返回新的集合。

map 操作不僅可以將元素變換成與之前類型相同的元素,也可以變化成與之前元素類型不同的元素,具體你想變換成什麼類型,這個是不做限制的。

 1fun main(args: Array<String>) {
 2    var list = listOf(1, 3, 4, 7, 89)
 3    var newList = list.map(Int::toDouble)
 4    newList.forEach(::println)
 5}
 6打印結果
 71.0
 83.0
 94.0
107.0
1189.0

flatMap:變換

 1fun main(args: Array<String>) {
 2    var list = listOf(1..20, 3..50, 4..100)
 3    var newList = list.flatMap {
 4        it
 5    }
 6    newList.forEach(::println)
 7}
 8打印結果
 91
102
11。。。
12100

flatMap 看起來跟 map 很相似,其實真的很類似,搞得有時候自己都不知道應該使用哪個操作符了,那就從源碼來看看它們之間的區別吧。

 1public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
 2    // 新創建一個ArrayList,將ArrayList跟transform傳遞給mapTo
 3    return flatMapTo(ArrayList<R>(), transform)
 4}
 5
 6public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
 7    for (element in this) {
 8        // 執行過transform以後的結果還是一個list集合,這裏就是map和flatMap之間的區別了
 9        val list = transform(element)
10        // 將變換後的集合整個添加到新的集合中
11        destination.addAll(list)
12    }
13    return destination
14}

可以看到 flatmap 中的參數 transform 是一個返回值爲 Iterable<R> 的函數,而 map 的參數 transform 是一個返回值爲 R 的函數。然後調用 flatMapTo 方法,將 transform 和一個新創建的 ArrayList 傳遞給了 flatMapTo 方法。在 flatMapTo 方法中,對當前的集合進行了迭代,然後將執行過變換操作後的集合數據全部添加到新的集合中,最終返回新的集合。

mapflatMap 的主要區別就是在於傳入的函數的返回值,一個是任意對象,一個是實現了 Iterable 接口的對象

reduce

例子:打印集合中的元素之和

 1fun main(args: Array<String>) {
 2    var listOf = listOf(1, 2, 3, 4, 5, 6, 7)
 3    var reduce = listOf.reduce { acc, i ->
 4        println("$acc   $i")
 5        acc + i
 6    }
 7    println(reduce)
 8}
 9打印結果:
101   2
113   3
126   4
1310   5
1415   6
1521   7
1628

還是直接對源碼進行分析吧,感覺看了源碼就一目瞭然了。

 1public inline fun <S, T: S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
 2    val iterator = this.iterator()
 3    // 如果當前的集合爲空就會拋出異常
 4    if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
 5    // 首先將集合中的第一個元素賦值給accumulator
 6    var accumulator: S = iterator.next()
 7    while (iterator.hasNext()) {
 8        // 執行operation,將accumulator和下一個元素傳遞給operation函數(也就是傳遞進行來的函數)
 9        // 然後將返回結果賦值給accumulator
10        accumulator = operation(accumulator, iterator.next())
11    }
12    return accumulator
13}

首先對當前的集合進行判空處理,接着將第一個元素賦值給 accumulatoraccumulator 的類型是 S 。然後對當前集合進行迭代處理,並調用我們傳遞進去的參數 operationoperation 函數中傳遞了兩個參數,一個是 S 類型的,一個是集合元素類型的。operation 函數的返回值也是 S 類型的,將 operation 的返回值重新賦值給 accumulator 。迭代完畢以後返回我們的 accumulator

其實通過我們解讀源碼以後,我們就可以知道 reduce 函數會將上一次的計算結果傳遞到下一次計算中,我們可以利用這個方式來實現以下字符串拼接,當然我們的字符串拼接有其他更好的方式,這裏只是做爲講解 reduce 的例子而已:

 1fun main(args: Array<String>) {
 2    var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d")
 3    var reduce = listOf.reduce { acc, i ->
 4        println("$acc   $i")
 5        "$acc$i"
 6    }
 7    println(reduce)
 8}
 9打印結果:
10H   e
11He   l
12Hel   l
13Hell   o
14Hello   W
15HelloW   o
16HelloWo   r
17HelloWor   l
18HelloWorl   d
19HelloWorld

fold:能夠添加初始值的reduce

不得不說,foldreduce 的作用基本是一致的,只是 fold 能夠添加初始值,什麼叫做能夠添加初始值呢?讓我們來舉個栗子看看唄!

 1fun main(args: Array<String>) {
 2    var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d")
 3    var fold = listOf.fold("老鐵說:") { acc, i ->
 4        println("$acc   $i")
 5        "$acc$i"
 6    }
 7    println(fold)
 8}
 9打印結果:
10老鐵說:   H
11老鐵說:H   e
12老鐵說:He   l
13老鐵說:Hel   l
14老鐵說:Hell   o
15老鐵說:Hello   W
16老鐵說:HelloW   o
17老鐵說:HelloWo   r
18老鐵說:HelloWor   l
19老鐵說:HelloWorl   d
20老鐵說:HelloWorld

還是看源碼吧:

1public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
2    // 將初始值賦值給 accumulator 
3    var accumulator = initial
4    // 集合進行遍歷操作,將accumulator和遍歷到數據傳遞給 operation 函數,執行operation以後,
5    // 將operation函數的返回值有賦值給了accumulator,接着下一步的遍歷
6    for (element in this) accumulator = operation(accumulator, element)
7    return accumulator
8}

看着源碼就會覺得這些函數的操作很是簡單了。fold 函數還有很多的兄弟:

  • foldRight 說的再多也不如看結果 1fun main(args: Array<String>) { 2 var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d") 3 // 注意!!!注意!!!注意!!!這邊的參數跟fold函數調用的參數位置是相反的,具體原因可以看源碼 4 var reduce = listOf.foldRight("老鐵說:") { i, acc -> 5 println("$acc $i") 6 "$acc$i" 7 } 8 println(reduce) 9 10 val add5 = add(5) 11 println(add5(2)) 12} 13打印結果: 14老鐵說: d 15老鐵說:d l 16老鐵說:dl r 17老鐵說:dlr o 18老鐵說:dlro W 19老鐵說:dlroW o 20老鐵說:dlroWo l 21老鐵說:dlroWol l 22老鐵說:dlroWoll e 23老鐵說:dlroWolle H 24老鐵說:dlroWolleH
  • foldRightIndexed 這個函數就是多了一個 index 的參數,具體的用處暫時沒有發現,就不做數據打印了。

filter:過濾

例子:過濾集合中的奇數

 1fun main(args: Array<String>) {
 2    var listOf = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
 3    var filter = listOf.filter { it % 2 == 0 }
 4    filter.forEach(::println)
 5}
 6打印結果:
 72
 84
 96
108

上源碼:

 1public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
 2    // 新創建一個ArrayList,將ArrayList和predicate函數一起傳遞給filterTo
 3    return filterTo(ArrayList<T>(), predicate)
 4}
 5
 6public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
 7    // 爲了保證代碼跟源碼的樣式一致,這裏就不做對代碼的樣式修改
 8    // 對當前的數組進行遍歷,如果滿足predicate(element)條件,就將當前元素加入到新的集合中
 9    for (element in this) if (predicate(element)) destination.add(element)
10    // 將新的集合返回
11    return destination
12}

filter 中創建新的集合 ArrayList ,將 ArrayListpredicate 函數一併傳遞給 filterTo 函數。在 filterTo 函數中,先對當前的集合進行遍歷,如果滿足條件 predicate(element) 就將當前的元素添加到新的集合中, predicate(element) 就是我們傳遞進來的那個函數,返回值是一個 Boolean 類型的。

takeWhile:截取集合中的數據,直到第一個不滿足條件的元素爲止

例子:截取集合中不能夠被5整除的數,直到第一個不滿足條件的元素爲止。

 1fun main(args: Array<String>) {
 2    var listOf = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
 3    var takeWhile = listOf.takeWhile { it % 5 != 0 }
 4    takeWhile.forEach(::println)
 5}
 6打印結果:
 71
 82
 93
104

源碼:

 1public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
 2    // 新創建一個集合ArrayList
 3    val list = ArrayList<T>()
 4    // 遍歷當前集合
 5    for (item in this) {
 6        if (!predicate(item))
 7            // 如果不滿足條件結束遍歷,也不會將當前不滿足條件的元素添加到新的集合中
 8            break
 9        // 將滿足條件的元素添加到集合中
10        list.add(item)
11    }
12    // 返回新創建的集合
13    return list
14}

let:將調用者當做參數傳遞給指定的函數

例子:省略if空判斷

 1fun main(args: Array<String>) {
 2    // 獲取一個Person對象,這個對象是可空的
 3    var person = getPerson()
 4    // 如果person爲空,將不會調用let函數,也不會去執行後面的代碼
 5    // 如果不使用let函數的話,只能採取以下寫法:
 6    //    if (person != null) {
 7    //        printPerson(person)
 8    //        println(person.name)
 9    //        println(person.age)
10    //    }
11    // 當然這個例子真的只是例子,暫時還是不能區別出使用let方式的優勢,只有用到複雜的邏輯實戰中才能體現出
12    person?.let {
13        printPerson(person)
14        println(person.name)
15        println(person.age)
16    }
17}
18
19fun printPerson(person: Person){
20     // 操作person   
21}
22
23fun getPerson(): Person? {
24    return null
25}
26
27class Person(var name: String, var age: Int) {
28}

源碼:

[email protected]
2public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

源碼也是很簡單,就是將調用者傳遞給傳入進來的函數並執行傳入進來的函數。

apply:執行指定函數,並且返回調用者

例子:修改person類的age屬性

1fun main(args: Array<String>) {
2    var person = Person("laotie",18)
3    var apply = person?.apply {
4        this.age = 16
5    }
6    println(apply)
7}
8data class Person(var name: String, var age: Int) 

上訴的例子真的只是例子,它沒有跟你講 apply 有多強大,它只是描述了的作用。源碼:

[email protected]
2public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

就是單純的執行函數 block 並返回調用者。

with

例子:文件讀取

 1fun main(args: Array<String>) {
 2    var reader = BufferedReader(FileReader("build.gradle"))
 3    with(reader){
 4        var line:String?
 5        while (true){
 6            // this 對象就是reader
 7            line = this.readLine()?:break
 8            println(line)
 9        }
10    }
11}
12// 結果就不打印了

源碼:

[email protected]
2public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

with 接收兩個參數,一個是 receiver ,上訴例子中就是 reader ,另一個就是 函數 ,在上訴例子中,我們使用了 Lambda 表達式,所以這個函數就移到了括號外面了。

疑惑點

函數定義中 `T.() -> Unit` 和 `() -> Unit` 的區別

我們一般定義函數都會選擇定義

1fun <T> T.makeMoney2(block: () -> Unit) {
2    block()
3}

上訴代碼表示:T 的擴展方法 makeMoney 接收一個 block 的函數,該函數是無參無返回的。

那我們再見識見識 T.() -> Unit 這種方式定義的方法,其實也很多見了:

1public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

可以看到 apply 函數使用的是 T.() -> Unit 這種方式,他們兩到底有啥區別呢?

我在 ScrollingActivity 定義了兩個方法:

1fun <T> T.makeMoney1(block: T.() -> Unit) {
2    block()
3}
4
5fun <T> T.makeMoney2(block: () -> Unit) {
6    block()
7}

分別使用 Button 調用這兩個方法試試:

從圖片中可以看出 :makeMoney1 中的 this 對象指的是調用對象 ,也就是 button ,而 makeMoney2 沒有提示,那麼我們就看打印吧:

1android.widget.Button{bb0caec VFED..C.. ......I. 0,0-0,0}
2cn.fengrong.kotlindemo.ScrollingActivity@c1cbf32

原來 makeMoney2 方法中的 this 對象指的是外部對象,在這裏就是我們的ScrollingActivity 對象。

總結

這兩個函數唯一的區別就是 T.()-Unit()->Unit 的區別,我們調用時,在代碼塊裏面寫this,的時候,根據代碼提示,我們可以看到,連個this代表的含義不一樣,T.()->Unit 裏的this代表的是自身實例,而 ()->Unit 裏,this代表的是外部類的實例

感謝

[Kotlin中,函數作爲參數,T.()->Unit 和 ()->Unit 的區別][https://www.jianshu.com/p/88a656e59c61]

This is All

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章