如果一个函数接收另外一个函数作为参数,或者返回值的类型是另外一个函数,那么该函数就称为高阶函数.
这个定义可能不太好理解,一个函数怎么能接收另一个函数作为参数呢?这就涉及另外一个概念了: 函数类型.
我们知道,编程语言中有整型、布尔型等字段类型,而Kotlin又增加了一个函数类型的概念.如果我们将这种函数类型添加到一个函数的参数声明或者返回值声明当中,那么这就是一个高阶函数了.
那,如何定义一个函数类型呢?
基本规则如下:
(String,Int) ->Unit
->左边的部分是用来声明该函数接收什么参数,多个参数之间使用逗号隔开,如果不接收任何参数,写一对空括号就可以了.
而 ->右边的部分用于声明该函数的返回值是什么类型,如果没有返回值就使用Unit,它大致相当于Java中的void.
现在将上述函数类型添加到某个函数的参数声明或者返回值声明中,那么这个函数就是一个高阶函数了.如下所示:
fun example(func: (String,Int) ->Unit){
func("hello",123)
}
可以看到,这里的example()函数接收了一个函数类型的参数,因此example() 函数就是一个高阶函数.而调用一个函数类型的参数,它的语法类似于调用一个普通的函数,只需要在参数名的后面加上一对括号,并在括号中传入必要的参数即可.
我们先定义高阶函数,如下:
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
这是一个非常简单的高阶函数,可能它没有多少实际的意义,却是个很好的学习示例. num1AndNum2() 函数的前两个参数没有什么好解释的,第三个参数是一个接收两个整型参数并且返回值也是整型的函数类型参数. 在num1AndNum2()函数中,我们没有进行任何具体的运算操作,而是将num1和num2 参数传给了第三个函数类型参数,并获取它的返回值,最终将得到的返回值返回.
如何调用呢? 我们添加如下代码:
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}
这里定义了两个函数,并且这两个函数的参数声明和返回值都和num1AndNum2()函数中的函数类型是完全匹配的.其中,plus()函数将两个参数相加并返回,minus()函数将两个参数相减并返回,分别对应了两种不同的运算操作.
有了上述函数之后,我们就可以调用num1AndNum2()函数了,代码如下:
val num1 = 100
val num2 = 80
val result = num1AndNum2(num1, num2, ::plus)
val result2 = num1AndNum2(num1, num2, ::minus)
注意这里调用num1AndNum2()函数的方式,第三个参数使用了 ::plus
和 ::minus
这种写法.这是一种函数引用方式的写法,表示将plus( )和minus( )函数作为参数传递给num1AndNum2( )函数.而由于num1AndNum2( )函数中使用了传入的函数参数来决定具体的运算逻辑,因此这里实际上分别使用了plus( )和minus( )函数来对两个数字进行运算.
使用这种函数引用的写法虽然能够正常工作,但是如果每次调用任何高阶函数的时候都还得先定义一个与其函数类型参数相匹配的函数,这是不是有些复杂了?
没错,因此Kotlin还支持其他多种方式来调用高阶函数,比如Lambda表达式、匿名函数、成员引用等.
其中,Lambda表达式是最常见也是最普通的高阶函数调用方式,也是我们接下来要重点学习的内容.
val num1 = 100
val num2 = 80
val result = num1AndNum2(num1, num2) {
n1, n2 -> n1 + n2 }
val result2 = num1AndNum2(num1, num2) {
n1, n2 -> n1 - n2 }
从上述可以看出,Lambda表达式同样可以完整地表达一个函数的参数声明和返回值声明(Lambda表达式中的最后一行代码会自动作为返回值),但是写法却更加精简.
下面我们继续对高阶函数进行探究.
在学习了apply函数时,它可以用于给Lambda表达式提供一个指定的上下文,当需要连续调用同一个对象的多个方法时,apply函数可以让代码变得更加精简,比如StringBuilder就是一个典型的例子.
接下来我们就使用高阶函数模仿实现一个类似的功能.
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
这里我们给StringBuilder类定义了一个build拓展函数,这个扩展函数接收一个函数类型参数,并且返回值类型也是StringBuilder.
注意,这个函数类型参数的声明方式和我们前面学习的语法有所不同:它在函数类型的前面加上了一个StringBuilder.
的语法结构.
这是什么意思呢?其实这就是定义高阶函数完整的语法规则,在函数类型的前面加上ClassName.
就表示这个函数类型是定义在哪个类当中的.
那么我们将函数类型定义到StringBuilder类当中有什么好处呢?好处就是当我们调用build函数时传入的Lambda表达式将会自动拥有StringBuilder的上下文,同时这也是apply函数的实现方式.
现在我们就可以使用自己创建的build函数来简化StringBuilder构建字符串的方式了.代码如下:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().build {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
}
可以看到,build函数的用法和apply函数基本上是一模一样的,只不过我们编写的build函数目前只能作用于StringBuilder类上面,而apply函数时可以作用在所有类上面的.如果想实现apply函数的这个功能,需要借助于Kotlin的泛型才行,相关内容下篇学习.