定义高阶函数
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数
基本规则:(String, Int) -> Unit
->左边部分用来声明该函数接收什么参数,右边声明返回值类型,没有返回值使用Unit,相当于Java中的void
将上述函数类型添加到某个函数的参数声明或者返回值声明上,那么这个函数就是一个高阶函数了,如:
fun example(func: (String, Int) -> Unit) {
func("hello", 123)
}
调用一个函数类型的参数,语法类似于调用一个普通的函数,只需要在参数名后面家伙少年宫一堆括号,并在括号中传入必要的参数即可。
例子
定义一个叫做num1AndNum2()的高阶函数,并让他接收两个整形和一个函数类型的参数。 新建一个HigherOrderFunction.kt文件:
代码语言:javascript复制package zyz.one
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
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")
}
这里调用num1AndNum2()函数中第三参数使用了::plus和::minus写法。这是一种函数引用方式的写法,表示将plus()和minus()函数做为参数传递给num1AndNum2()。
使用Lambda表达式
代码语言:javascript复制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")
}
添加以下代码
代码语言:javascript复制fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
我们给StringBuilder类定义了一个build拓展韩束,这个拓展函数接收一个函数类型参数,并且返回值也是StringBuilder。
在函数类型的前面加上ClassName.就表示这个函数类型是定义在哪个类当中的。
将函数类型定义在StringBuilder中的好处为当我们代用buil函数时传入的Lambda表达式将会自动拥有StringBuilder的上下文,同时这也是apply函数的实现方式。
现在我们使用自己创建的build函数来简化StringBuilder构建字符串的方式
代码语言:javascript复制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类上
内联函数的作用
高阶函数的实现原理
代码语言:javascript复制fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
fun main() {
val num1 = 100
val num2 = 80
val result = num1AndNum2(num1, num2) { n1, n2 ->
n1 n2
}
}
解释成Java字节码大概为:
代码语言:javascript复制public static int num1AndNum2(int num1, int num2, Function operation) {
int result = (int) operation.invoke(num1, num2);
return result;
}
public static void main() {
int num1 = 100;
int num2 = 80;
int result = num1AndNum2(num1, num2, new Function() {
@Override
public Integer invoke(Integer n1, Integer n2) {
return n1 n2;
}
};
}
这里可以看到Lambda表达式是创建匿名类实现,也就是说我们没创建一个Lambda表达式就会创建一个新的匿名类,造成额外的内存和性能开销。
为了解决这个问题,Kotlin提供了内联函数,他可以将Lambda表达式带来的运行时开销完全消除
内联函数用法
只需要在定义高阶函数时加上inline关键字的声明即可:
代码语言:javascript复制inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
内联函数的工作原理: Kotlin编译器会将内联函数中的代码在编译时候自动替换到调用它的地方,这样就不存在运行时开销了。
noinline与crossinline
在不想被内联的函数类型前加noinline
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
}
内联函数局限性 内联函数因为是使用代码进行替换,所以其没有函数类型的参数,也就不支持把函数类型的参数传递给别的函数。
例子
内联函数引起的Lambda表达式中是可以使用return进行函数返回的,而非内联函数只能进行局部返回
代码语言:javascript复制package zyz.one
fun printString(str: String, block: (String) -> Unit) {
println("printString begin")
block(str)
println("printString end")
}
fun main() {
println("main start")
val str = ""
printString(str) { s ->
println("lambda start")
if (s.isEmpty()) return@printString
println(s)
println("lambda end")
}
println("main end")
}
结果: main start printString begin lambda start printString end main end
如果字符串为空则不打印,因为Lambda中不允许使用return,这里使用了return@printlnString这种写法进行局部返回,并不再执行Lambda下面代码。
如果将printlnString声明为一个内联函数情况就不一样了
代码语言:javascript复制package zyz.one
inline fun printString(str: String, block: (String) -> Unit) {
println("printString begin")
block(str)
println("printString end")
}
fun main() {
println("main start")
val str = ""
printString(str) { s ->
println("lambda start")
if (s.isEmpty()) return
println(s)
println("lambda end")
}
println("main end")
}
结果: main start printString begin lambda start
这里return代表的是返回外层的调用函数也就是main()函数。
绝大多数高阶函数是可以直接声明成内联函数的,但也有少部分例外情况:
代码语言:javascript复制inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
此时会报错,因为block()调用是在Lambda中,而Lambda是匿名函数,内联函数允许return进行返回,但是我们是在匿名类中调用的函数类型参数,此时是不可能进行外层调用函数进行返回的,最多只能对匿名类中的函数调用进行返回。
也就是说,如果我们在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现中调用了函数类型参数,此时在将高阶函数声明成内联函数就会报错。
上述情况也可以声明称内联函数借助crossinline:
代码语言:javascript复制inline fun runRunnable(crossinline block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
前面的错误是因为内联函数中的Lambda表达式允许使用return,但是高阶函数的匿名类不允许使用return,这就造成了冲突。 crossline保证Lambda表达式中一定不会使用return,这样冲突就解决了。 声明了crossline我们就不允许使用return,但是可以使用return@就行局部返回