①前言
通过前面两篇文章的介绍,我们对kotlin有了初步的了解(没看的同学可以通过点击话题跳转回去阅读),现在去动手写kotlin代码或者阅读一些kotlin源码应该不成问题了。今天我们主要围绕函数来展开讲解一下kotlin的函数。
在Java中的函数比较简单,通常情况下我们会将某些特定的操作(为了达成某个目的的一段代码逻辑)封装成一个函数,方便不同的地方调用。当然在kotlin中函数也是同样的作用了。
要点:
高阶函数的概念 高阶函数配合内联函数使用优点 几个非常有用的高阶函数 集合变换 filter map fold 与Java8的stream比较(饿汉式和懒汉式)
②高阶函数的概念
概念:所谓高阶函数,是指它可以接受一个函数作为参数,或者返回值为函数。
其实之前的文章中已经用过了,比如集合的filter函数,看下当时我们是怎么使用的:
代码语言:javascript复制val youngPersonList = users.filter { it.age < 20 }
在集合users里面过滤出年龄小于20岁的人,这其实就是调用参数为函数类型的高阶函数,我们看下filter的源码:
代码语言:javascript复制/**
* Returns a list containing only elements matching the given [predicate].
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
可以看到filter函数接受一个参数,参数的类型是 (T) -> Boolean ,这种就是kotlin中的函数类型,其中的T为函数的参数类型,Boolean是函数的返回类型,也就是说,我们的调用最开始应该是这个样子的:
代码语言:javascript复制users.filter({user:User ->
user.age < 20
})
上面这种就是一种创建函数类型实例的一种方式:Lambda表达式,基于kotlin的一些规约:
- Lambda 表达式包含在大括号之内, 在完整语法形式中, 参数声明在大括号之内, 参数类型的声明是可选的, 函数体在 -> 符号之后. 如果 Lambda 表达式自动推断的返回值类型不是 Unit, 那么 Lambda 表达式函数体中, 最后一条(或者就是唯一一条)表达式的值, 会被当作整个 Lambda 表达式的返回值.
- 如果函数的最后一个参数是一个函数, 那么如果使用 Lambda 表达式作为这个参数的值, 可以将 Lambda 表达式写在函数调用的括号之外
- 如果 Lambda 表达式是函数调用时的唯一一个参数, 括号可以完全省略
- 如果编译器能够自行识别出 Lambda 表达式的参数定义, 那么我们可以不必声明这个唯一的参数, 并省略 -> 符号, 这个参数会隐含地声明为 it
所以,最后我们省略了不必要的部分,调用高阶函数filter时就成了一行users.filter { it.age < 20 },注意函数的调用是需要有函数栈的操作的,所以我们一般高阶函数都会声明成内联的。
③常用的函数
接下来我们还要介绍一些对我们比较有用常用的高阶函数:
let、run、also、apply这四个用法差不多,简单写个应用的场景看一下:
代码语言:javascript复制intent.extras?.let {
it.getString("")
it.getInt("")
it.getLong("")
}
相信大家一定都有这样的经历,在Activity的onCreate()中,通过intent获取extra中的数据,通过安全调用符和let函数,我们就可以安心的在lambda的函数体中使用extras。
接下来重点说下use函数,需要注意的是use是针对Closeable的扩展函数,所以只能通过Closeable的实例来调,use函数为我们节省了大量的代码,我们在操作文件流的时候总是会写一些try catch以及关闭流的操作,使用use后,这些都不需我们手动写,use帮我们做了,以从文件中读流的例子:
代码语言:javascript复制File("build.gradle").inputStream().reader().buffered().use {
println(it.readLines())
}
只要上面的lambda表达式执行完的时候,相关的close操作会自动被执行,这个我们可以看下use的源码就清楚了:
不仅帮我们trycatch了,关流的操作也一点不含糊。一个字:爽
④常用的几个集合扩展函数
前面多次提及集合的filter函数,其实有几个集合变换的函数我们需要拿出来分析一下,首先,当然还是filter:
filter就是将一个集合通过一个函数来过滤出另一个集合,这个我们都很容易理解。
接下来,我们看下map这个函数,通过直译过来就是映射的意思,函数的作用确实也是如此,该函数通过遍历集合的每一个元素,通过函数体里面的逻辑一一处理后,生成新的一个集合,过程如下图所示:
其实Java里面也提供了相似的方法,从Java8开始,提供了stream()函数,可以做流水线的操作,如下图所示:
上图中数据的流程是,list进行遍历,当遍历到第二个元素,满足filter的条件,将第二个元素的值 2 送到map当中,map通过自己的算法得出5,将5放到forEach方法中输出,发现没有,它并没有在filter中全部遍历完再送到map函数,而是生产出一个结果就输送到流水线的下个函数。
当然了kotlin也可以实现一样的结果,通过asSequence函数,如下所示:
但是,上面的流水线操作一定要在最后调用forEach(流水线开关),如果不调用forEach的话,filter和map也是不会调用的,通常称作懒序列,当然一些聚合操作也可以充当开关。
但是kotlin显然可以不用调用asSequence函数,通过集合直接调用,那么如果是直接通过集合来调用,显然就相当于先调用filter函数,全部执行完返回一个列表,然后新的列表集合调用map,以此类推最后调用到forEach函数。这个和asSequence显然不同,我们称为饿汉式。
除了集合变化的操作,还有一些集合聚合的操作:sum、reduce、fold,以fold函数为例,fold函数提供两个参数,第一个参数是一个初始值,第二个参数是进行操作的函数,第一次运算的时候是以第一个参数也就是初始值和你集合中的第一个元素进行运算,得出的值作为第二轮计算的初始值,再和集合的第二个元素进行运算,以此类推,直到最后计算出一个值(不是集合)。可以参考下方的示意图:
reduce函数基本和fold函数一样,只不过没有初始值,这里就不再分析了。