前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin的高阶函数和常用高阶函数

Kotlin的高阶函数和常用高阶函数

作者头像
用户2802329
发布2018-12-05 18:13:57
1.7K0
发布2018-12-05 18:13:57
举报
文章被收录于专栏:Android先生

高阶函数的定义

将函数当做参数或者是返回值的函数

什么是高阶函数

可以看看我们常用的 forEach 函数:

代码语言:javascript
复制
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 函数:

代码语言:javascript
复制
1fun main(args: Array<String>) {
2    args.forEach(::println)
3}

调用的时候,我们将 println 函数传递给了 forEach 函数,这里采用的是函数引用 。就上诉代码,我们还可以结合 Lambda 表达式来进行处理:

代码语言:javascript
复制
 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 的值:

代码语言:javascript
复制
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

代码语言:javascript
复制
1class Hello {
2    fun world() {
3        println("Hello World.")
4    }
5}

然后进行以下操作:

可以看到 Android Studio 分别对 helloWorldprinter 的解释:

  • helloWorld 是一个方法,然后参数类型为 Hello ,返回值为 Unit
  • printer 也是一个方法,但是参数有两个,分别是 PdfPrinterAny 类型, 返回值为 Unit

forEach 中,只有一个参数传递,但是 PdfPrinter::println 需要的是两个参数,肯定就会报错,所以我们需要对此进行修改:

代码语言:javascript
复制
1fun main(args: Array<String>) {
2    var pdfPrinter = PdfPrinter()
3    args.forEach(pdfPrinter::println)
4}

这个样子就OK了。

常用高阶函数

map:变换

通常我们会使用以下的方式来实现对集合中的元素进行修改的操作:

代码语言:javascript
复制
 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」:

代码语言:javascript
复制
 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 的实现原理:

代码语言:javascript
复制
 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 操作不仅可以将元素变换成与之前类型相同的元素,也可以变化成与之前元素类型不同的元素,具体你想变换成什么类型,这个是不做限制的。

代码语言:javascript
复制
 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:变换
代码语言:javascript
复制
 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 很相似,其实真的很类似,搞得有时候自己都不知道应该使用哪个操作符了,那就从源码来看看它们之间的区别吧。

代码语言:javascript
复制
 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

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

代码语言:javascript
复制
 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

还是直接对源码进行分析吧,感觉看了源码就一目了然了。

代码语言:javascript
复制
 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 的例子而已:

代码语言:javascript
复制
 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 能够添加初始值,什么叫做能够添加初始值呢?让我们来举个栗子看看呗!

代码语言:javascript
复制
 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

还是看源码吧:

代码语言:javascript
复制
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:过滤

例子:过滤集合中的奇数

代码语言:javascript
复制
 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

上源码:

代码语言:javascript
复制
 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整除的数,直到第一个不满足条件的元素为止。

代码语言:javascript
复制
 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

源码:

代码语言:javascript
复制
 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空判断

代码语言:javascript
复制
 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}

源码:

代码语言:javascript
复制
1@kotlin.internal.InlineOnly
2public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

源码也是很简单,就是将调用者传递给传入进来的函数并执行传入进来的函数。

apply:执行指定函数,并且返回调用者

例子:修改person类的age属性

代码语言:javascript
复制
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 有多强大,它只是描述了的作用。源码:

代码语言:javascript
复制
1@kotlin.internal.InlineOnly
2public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

就是单纯的执行函数 block 并返回调用者。

with

例子:文件读取

代码语言:javascript
复制
 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// 结果就不打印了

源码:

代码语言:javascript
复制
1@kotlin.internal.InlineOnly
2public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

with 接收两个参数,一个是 receiver ,上诉例子中就是 reader ,另一个就是 函数 ,在上诉例子中,我们使用了 Lambda 表达式,所以这个函数就移到了括号外面了。

疑惑点

函数定义中 `T.() -> Unit` 和 `() -> Unit` 的区别

我们一般定义函数都会选择定义

代码语言:javascript
复制
1fun <T> T.makeMoney2(block: () -> Unit) {
2    block()
3}

上诉代码表示:T 的扩展方法 makeMoney 接收一个 block 的函数,该函数是无参无返回的。

那我们再见识见识 T.() -> Unit 这种方式定义的方法,其实也很多见了:

代码语言:javascript
复制
1public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

可以看到 apply 函数使用的是 T.() -> Unit 这种方式,他们两到底有啥区别呢?

我在 ScrollingActivity 定义了两个方法:

代码语言:javascript
复制
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 没有提示,那么我们就看打印吧:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT先森养成记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 高阶函数的定义
  • 什么是高阶函数
  • 常用高阶函数
    • map:变换
      • flatMap:变换
        • reduce
          • fold:能够添加初始值的reduce
            • filter:过滤
              • takeWhile:截取集合中的数据,直到第一个不满足条件的元素为止
                • let:将调用者当做参数传递给指定的函数
                  • apply:执行指定函数,并且返回调用者
                    • with
                    • 疑惑点
                      • 函数定义中 `T.() -> Unit` 和 `() -> Unit` 的区别
                      • 感谢
                      • This is All
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档