写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kotlin的同学。系列文章的知识点会以《Kotlin实战》这本书中顺序编写,在将书中知识点展示出来同时,我也会添加对应的Java代码用于对比学习和更好的理解。
Kotlin教程(一)基础
Kotlin教程(二)函数
Kotlin教程(三)类、对象和接口
Kotlin教程(四)可空性
Kotlin教程(五)类型
Kotlin教程(六)Lambda编程
Kotlin教程(七)运算符重载及其他约定
Kotlin教程(八)高阶函数
声明高阶函数
高阶函数就是以另外一个函数作为参数或者返回值的函数。在Kotlin中,函数可以用lambda或者函数引用来表示。因此,任何以lambda或者函数引用作为参数的函数,或者返回值为lambda或函数引用的函数,都是高阶函数。例如,标准库中的filter函数将一个判断式作为参数:
list.filter { x > 0 }
函数类型
为了声明一个以lambda作为实参的函数,你需要知道如何声明对应形参的类型。在这之前,我们先来看一个简单的例子,把lambda表达式保存在局部变量中。其实我们已经见过在不声明类型的情况下如何做到这一点,这依赖于Kotlin的类型推导:
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
编译器推导出sum和action这两个变量具有函数类型。现在我们来看看这些变量的显示类型声明是什么样子的:
val sum: (Int, Int) -> Int = { x, y -> x + y }
val action: () -> Unit = { println(42) }
声明函数类型,需要将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型。
你应该还记得Unit类型用于表示函数不返回任何有用的值。在声明一个普通的函数时,Unit类型的返回值是可以省略的,但是一个函数类型声明总是需要一个显式地返回类型,所以这里Unit是不能省略的。
在lambda表达式{ x, y -> x + y }
中省略参数了类型,因为他们的类型已经在函数类型的变量声明部分指定了,不需要在lambda本身的定义中再重复声明。
就像其他方法一样,函数类型的返回值也可以标记为可空类型:
var canReturnNull: (Int, Int) -> Int? = { null }
也可以定义一个函数类型的可空变量,为了明确表示是变量本身可空,而不是函数类型的返回类型可空,你需要将整个函数类型的定义包含在括号内并在括号后面添加一个问号:
var funOrNull: ((Int, Int) -> Int)? = null
注意这两个例子的微妙区别。如果省略了括号,声明的将会是一个返回值可空的函数类型,而不是一个可空的函数类型的变量。
函数类型的参数名
可以为函数类型声明中的参数指定名字:
fun performRequest(
url: String,
callback: (code: Int, content: String) -> Unit //给函数类型的参数定义名字
) {
/*...*/
}
>>> val url = "http://kotl.in"
>>> performRequest(url) {code, content -> /*...*/} //可以使用定义的名字
>>> performRequest(url) {code, page -> /*...*/} //也可以改变参数名字
参数名称不会影响类型的匹配。当你声明一个lambda时,不必使用和函数类型声明中一模一样的参数名称,但命名会提升代码可读性并且能用于IDE的代码补全。
调用作为参数的函数
知道了怎样声明一个高阶函数,现在我们拉讨论如何去实现它。第一个例子会尽量简单并且使用之前的lambda sum 同样的声明。这个函数实现两个数字2和3的任意操作,然后打印结果。
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
>>> twoAndThree { a, b -> a + b }
The result is 5
>>> twoAndThree { a, b -> a * b }
The result is 6
调用作为参数的函数和调用普通函数的语法是一样的:把括号放在函数名后,并把参数放在括号内。
来看一个更有趣的例子,我们来实现最常用的标准库函数:filter函数。为了让事情简单一点,将实现基于String类型的filter函数,但和作用与几何的泛型版本的原理是相似的:
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
filter函数以一个判断是作为参数,判断是的类型是一个函数,以字符作为参数并返回Boolean类型的值。如果让传递给判断式的字符出现在最终返回的字符串中,判断式需要返回true,反之返回false。
filter函数的实现非常简单明了。它检查每一个字符是否符合满足判断式,如果满足就将字符添加到包含结果的StringBuilder中。
在Java中使用函数类
其背后的原理是,函数类型被声明为普通的接口,一个函数类型的变量是FunctionN接口的一个实现。Kotlin标准库定义了一系列的接口,这些接口对应于不同参数数量的函数:Function0<R>
没有参数的函数、Function1<P1,R>
一个参数的函数等等。每个接口定义了一个invoke方法,调用这个方法就会执行函数。一个函数类型的变量就是实现了对应的FunctionN接口的实现类的实例,实现了类的invoke方法包含了lambda函数体。
在Java中可以很简单的调用使用了函数类型的Kotlin。Java 8的lambda会被自动转换为函数类型的值。