高阶函数和lambda表达式
所谓的高阶函数,你可以把它理解成“ 以其他函数作为参数或返回值的函数” 。
函数类型
从中我们发现,Kotlin中的函数类型声明需遵循以下几点:
-
通过->符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型;
-
必须用⼀个括号来包裹参数类型;
-
返回值类型即使是Unit,也必须显式声明。
如果是多个参数的情况,那么我们就需要用逗号来进行分隔,如:
此外,Kotlin还支持为声明参数指定名字,如下所示:
支持可空类型:
如果该函数类型的变量也是可选的话,我们还可以把整个函数类型变成可选:
函数类型作为一个函数的返回值(高阶函数):
这表示传入⼀个类型为Int的参数,然后返回另⼀个类型为(Int)->Unit的函数。
方法和成员引用
此外,我们还可以直接通过这种语法,来定义⼀个类的构造方法引用变量。
其中getBook的类型为(name: String)-> Book,类似的可以引用类中的某个成员变量,如:
这对于在对Book类对象的集合应用⼀些函数式API的时候,会显得格外有用,比如:
匿名函数
lambda表达式
现在用Lambda的形式来定义一个加法操作:
Lambda语法总结:
-
⼀个Lambda表达式必须通过{}来包裹;
-
如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明;
-
如果Lambda变量声明了函数类型,那么Lambda的参数部分的类型就可以省略。
单个参数的隐式名称:it
它也是Kotlin简化Lambda表达的⼀种语法糖,it是item的缩写。
Function类型
可见每个Function类型都有一个invoke方法。
- 22是业界的⼀种设计惯例,如Scala中也是22。
- 在Kotlin中除了23个常用的Function类型外,还有⼀个FunctionN。在参数真的超过22个的时候,我们就可以依靠它来解决问题。更多细节可以参考https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md。
在Kotlin中,我们还可以用更加简洁的方式,即使用括号调用来替代invoke,如下所示:
函数、Lambda和闭包
-
fun在没有等号、只有花括号的情况下,是我们最常见的代码块函数体,如果返回非Unit值,必须带return。
-
fun带有等号,是单表达式函数体。该情况下可以省略return。
-
不管是用val还是fun,如果是等号加花括号的语法,那么构建的就是⼀个Lambda表达式,Lambda的参数在花括号内部声明。所以,如果左侧是fun,那么就是Lambda表达式函数体,也必须通过()或 invoke来调用Lambda,如:
在Kotlin中,你会发现匿名函数体、Lambda(以及局部函数、object表达式)在语法上都存在“ {}”,由这对花括号包裹的代码块如果访问了外部环境变量则被称为⼀个闭包。⼀个闭包可以被当作参数传递或者直接使用,它可以简单地看成“ 访问外部环境变量的函数” 。Lambda是Kotlin中最常见的闭包形式。与Java不⼀样的地方在于,Kotlin中的闭包不仅可以访问外部变量,还能够对其进行修改。
类“柯里化”风格
此外,如果参数不止⼀个,且最后⼀个参数为函数类型时,就可以采用类似柯里化风格的调用:
它等价于以下的的调用方式:
函数可变参数
此外,我们可以使用 *(星号)来传入外部的变量作为可变参数的变量,如下:
可选参数
fun foo(a: Int, b: Int = 10) {
}
foo(2)
foo(2, 4)
当每个参数都有默认值时,需要指定参数名:
fun foo(a: Int = 3, b: Int = 10) {
}
foo(b = 2)
foo(a = 2, b = 4)
调用Java的函数式接口
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
…
}
})
以上的例子在Kotlin会被转化成这样:
view.setOnClickListener(object : OnClickListener {
override fun onClick(v: View) {
…
}
})
Kotlin允许对Java的类库做一些优化,任何函数接收了一个Java的SAM(单一抽象方法)都可以用Kotlin的函数进行替代。以上的例子我们可以看成在Kotlin定义了以下方法:
listener是一个函数类型的参数,它接收一个类型View的参数,然后返回Unit。我们可以用Lambda语法来简化它:
由于Kotlin存在特殊语法糖,这里的listener是setOnClickListener唯一的参数,所以我们就可以省略掉括号
带接收者的Lambda
此时,我们就可以用一个Int类型的变量调⽤sum方法,传入一个Int类型的参数,对其进行plus操作。
class HTML {
fun body() {...}
}
fun html(init: HTML.()->Unit): HTML {
val html = HTML() // 创建了接收者对象
html.init() // 把Lambda传递给接收者对象
return html
}
html {
body() // 调用接收者对象的body方法
}
with和apply
fun bindData(bean: ContentBean) {
val titleTV = findViewById<TextView>(R.id.iv_title)
val contentTV = findViewById<TextView>(R.id.iv_content)
with(bean) {
titleTV.text = this.title // this可以省略
titleTV.textSize = this.titleFontSize
contentTV.text = this.content
contentTV.text = this.contentFontSize
}
}
如果不使用with,我们就需要写好多遍bean。现在来看看with在Kotlin库中的定义:
可以看出,with函数的第1个参数为接收者类型,然后通过第2个参数创建这个类型的block方法。
与with函数不同,apply直接被声明为类型T的一个扩展方法,它的block参数是一个返回Unit类型的函数,作为对比,with的block则可以返回自由的类型。