高階函數的定義
將函數當做參數或者是返回值的函數
什麼是高階函數
可以看看我們常用的 forEach
函數:
1public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit { 2 for (element in this) action(element) 3}
首先我們可以知道, forEach
是 Array
的擴展函數,然後參數是 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
分別對 helloWorld
和 printer
的解釋:
helloWorld
是一個方法,然後參數類型爲Hello
,返回值爲Unit
printer
也是一個方法,但是參數有兩個,分別是PdfPrinter
和Any
類型, 返回值爲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
的功能,可以對集合中的元素進行你想要的操作,是不是跟 RxJava
的 map
很類似呢!我們來細看一下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的 ArrayList
和 transform
函數,在 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
方法中,對當前的集合進行了迭代,然後將執行過變換操作後的集合數據全部添加到新的集合中,最終返回新的集合。
map
和 flatMap
的主要區別就是在於傳入的函數的返回值,一個是任意對象,一個是實現了 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}
首先對當前的集合進行判空處理,接着將第一個元素賦值給 accumulator
,accumulator
的類型是 S
。然後對當前集合進行迭代處理,並調用我們傳遞進去的參數 operation
,operation
函數中傳遞了兩個參數,一個是 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
不得不說,fold
跟 reduce
的作用基本是一致的,只是 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老鐵說:dlroWolleHfoldRightIndexed
這個函數就是多了一個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
,將 ArrayList
和 predicate
函數一併傳遞給 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]