如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
fun example(func: (String, Int) -> Unit) {
func("hello", 123)
}
可以看到,这里的example()函数接收了一个函数类型的参数,因此example()函数就是一个高阶函数。而调用一个函数类型的参数,它的语法类似于调用一个普通的函数,只需要在参数名的后面加上一对括号,并在括号中传入必要的参数即可,并使用箭头符号,在后面声明返回类型,如果不返回则使用Unit。
这里我定义一个num1AndNum2的高阶函数,并让它接收两个整形和一个函数类型的参数,通过传入的函数类型参数决定它们进行什么运算。
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
接下来我们定义一下两个函数,这两个函数参数值声明和返回值声明都与num1AndNum2中的函数类型参数是完全匹配的
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}
接下来我们就可以进行调用了
fun main() {
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1, num2, ::plus)
val result2 = num1AndNum2(num1, num2, ::minus)
println("result1 is $result1")
println("result2 is $result2")
}
其中::plus,::minus是一种函数引用的写法,表示将plus(),minus()作为参数传递给num1AndNum2()
结果如下
这种用法虽然能够正常工作,但是如果每次调用高阶函数都得先定义一个与其函数类型参数匹配的函数,可能就会显得有些麻烦。
因此Kotlin还支持Lambda表达式、匿名函数、成员引用等方式来调用高阶函数
上述代码如果用Lambda表达式的写法来实现的话,代码如下
fun main() {
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
val result2 = num1AndNum2(num1, num2) { n1, n2 ->
n1 - n2
}
println("result1 is $result1")
println("result2 is $result2")
}
再以上面是代码为例,当我们使用Lambda表达式,在Kotlin代码转换成Java时大致会转化成一下代码,它底层的实现原理实际上是将num1AndNum2的第三个参数变成了一个Function接口,然后通过接口的invoke()函数调用,其实就是匿名类的用法。这就表明我们没调用一次Lambda表达式,都会创建一个新的匿名类实例,这样会造成额外的内存和性能开销
public static num1AndNum2(int num1,int num2, Function operation) {
int result =(int) operation.invoke(num1, num2);
return result;
}
public staric void main() {
int num1 = 100
int num2 = 80
int result1 = num1AndNum2(num1, num2, new Function() {
@Override
public Integer invoke(Integer n1,Integer n2){
return n1+n2;
}
});
}
为了解决这个问题,Kotlin提供了内联函数的功能,可以将使用Lambda表达式带来的运行时开销完全消除。
它的工作原理并不复杂,就是Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。
定义内联函数很简单,只需要在定义高阶函数时加上inline关键字的声明即可,如下所示:
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
首先,Kotlin编译器会将Lambda表达式中的代码替换到函数类型参数调用的地方
接下来,再将内联函数中的全部代码替换到函数调用的地方
最终的代码就被替换成了这样
如果我们的内联函数中,只想内联其中的某一个Lambda或者某个Lambda不想内联怎么办呢?这时我们就可以使用noline关键字了,它的用法也同样简单:
inline fun nolineTest(block1:() -> Unit,noinline block2:() ->Unit){
//...
}
此处我们的block2加上了noinline关键字,那么我们在使用的时候,就不会对这个函数类型参数进行内联了。
为了减少内存和性能上的开销,绝大多数的高阶函数都可以直接声明为内联函数,但为什么是绝大多数呢,接下来我们来看一个列外情况:
inline fun runRunnable(block:() -> Unit){
val runnable = Runnable{
block()
}
runnable.run()
}
可以看到提示使用crossinline关键字来解决这个问题。我们可以将代码改成这个样子,就不会有错误提示了:
inline fun runRunnable(crossinline block:() -> Unit){
val runnable = Runnable{
block()
}
runnable.run()
}
下面我们来分析一下问什么会有这个提示:
我们使用了crossinline关键字之后,Runnable的Lambda表达式中就不能再使用return关键字进行返回了,但是我们仍然可以使用return@runRunnable的写法进行局部的返回。除此之外,其它内联函数的特性仍然保留。