定义高阶函数
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数
定义函数类型
(String, Int) -> Unit
->
左侧声明了这个函数类型需要接收的参数类型->
右侧声明了这个函数类型返回值类型
其中Unit
表示没有返回值,相当于java
中的void
高级函数的语法规则
fun example(func: (String, Int) -> Unit){
func("hello",123)
}
其中example()
函数就是一个高阶函数,因为它接收了参数名为func
一个函数类型的参数,想使用这个函数类型时,只需要参数名后面加括号,括号中传入必要的参数就行了。
实践
先定义一个高阶函数
fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
return func(num1, num2)
}
在这个函数中,将num1
和num2
传给了第三个函数类型参数
再定义两个与这个函数类型参数完全匹配的函数
fun plus(num1: Int, num2: Int): Int{
return num1 + num2
}
fun minus(num1: Int,num2: Int): Int{
return num1 - num2
}
在main()
函数中进行调用
fun main(){
val num1 = 5
val num2 = 3
println("plus result is ${num1AndNum2(num1, num2, ::plus)}")
println("minus result is ${num1AndNum2(num1, num2, ::minus)}")
}
其中::plus
和::minus
是一种函数引用方式的写法。
Lambda表达式调用高阶函数
但如果每次调用任何高阶函数的时候都还得先定义一个与其函数类型参数相匹配的函数,就复杂了。
因此Kotlin还支持其他多种方式来调用高阶函数,比如Lambda表达式、匿名函数、成员引用等。其中,Lambda表达式是最常用也是最普遍的高阶函数调用方式。
将刚刚的代码换成Lambda表达式的方式调用高阶函数:
fun main(){
val num1 = 5
val num2 = 3
val result1 = num1AndNum2(num1, num2){ n1, n2 ->
n1 + n2 //Lambda表达式的最后一行会自动作为返回值
}
val result2 = num1AndNum2(num1, num2){ n1, n2 ->
n1 - n2
}
println("result1 is $result1")
println("result2 is $result2")
}
此时,删掉plus()
和minus()
函数,也能得到相同的结果:
高阶函数模仿apply用法
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder{
block()
return this
}
给StringBuilder
类定义了一个build
扩展函数,这个扩展函数接收一个函数类型参数,并且返回值类型也是StringBuilder
。
定义高阶函数完整的语法规则,在函数类型的前面加上ClassName.
就表示这个函数类型是定义在哪个类当中的,在上述代码中,函数类型就是定义在StringBuilder
类中的。
在main
函数中,使用build
函数简化StringBuilder
构建字符串方式:
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear")
val result = StringBuilder().build {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
}
内联函数的作用
Lambda表达式在底层被转换成了匿名类的实现方式。这就表明,每调用一次Lambda表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销。为了解决这个问题,Kotlin提供了内联函数的功能,它可以将使用Lambda表达式带来的运行时开销完全消除。
关键字:inline
inline fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
return func(num1, num2)
}
Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。
noinline
高阶函数中如果接收了两个或者更多函数类型的参数,这时给函数加上了inline
关键字, 那么Kotlin编译器会自动将所有引用的Lambda表达式全部进行内联。 但如果只想内联其中的一个Lambda表达式该怎么办呢?这时就可以使用noinline
关键字了,
inline fun inlineTest(block: () -> Unit, noinline block2: () -> Unit){
}
这里使用inline
关键字声明了inlineTest()
函数,原本block1
和block2
这两个函数类型参数所引用的Lambda表达式都会被内联。但是在block2
参数的前面又加上了⼀个noinline
关键字,那么现在就只会对block1
参数所引用的Lambda表达式进行内联了。
内联函数的局限性
内联的函数类型参数只允许传递给另外一个内联函数。
内联函数和非内联函数的区别
内联函数 | 非内联函数 |
函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数属性。 | 函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实的参数 |
引用的Lambda表达式中是可以使用 | 只能进行局部返回,关键字 |
crossinline
有些情况下,不能直接将高阶函数定义成内联函数,如下图:
在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误。
在函数类型参数的前面加上crossinline
的声明,代码就可以正常编译通过了。
crossinline
关键字用于保证在内联函数的Lambda表达式中一定不会使用return
关键字,声明了crossinline
之后,我们就无法在调用runRunnable
函数时的Lambda表达式中使用return
关键字进行函数返回了,但是仍然可以使用return@runRunnable
的写法进行局部返回。