Kotlin高阶函数

定义高阶函数

之前的的函数式AP会接受Lambda参数,同样我们也可以自定义一个函数式API,也就是高阶函数。

高阶函数的定义是一个函数接受另一个函数作为他的参数,或者返回值是一个函数,那这个函数就是高阶函数。

那么如何定义一个函数类型呢?基本规则就是:(String, Int) -> Unit。其中(String, Int)表示传的参数类型,如果没有也可以为空,Unit指的是没有返回值,也可以改成String,Int等。

比如我们定义一个num1AndNum2()高阶函数:

1
2
3
4
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1,num2)
return result
}

num1,num2很好理解就是两个参数,operation: (Int, Int) -> Int就是我们传的函数了,代表这个函数需要传两个Int类型的参数,然后返回值为Int,然后我们定义两个函数:

1
2
3
4
5
6
7
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}

fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}

这两个函数就符合operation这个函数类型,然后我们调用这个高阶函数:

1
2
3
4
5
6
7
fun main() {
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1,num2,::plus)
val result2 = num1AndNum2(num1,num2,::mini)
println("result1 is $result1, result2 is $result2")
}

其中第三个参数::plus是函数引用方式的写法,代表将这个函数作为参数传了进去。运行结果如下:

高阶函数运行结果

既然我们传的是函数参数类型,我们也可以直接用lambda表达式直接传进去,就会改成:

1
2
3
4
5
6
7
8
9
10
11
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, result2 is $result2")
}

这段代码表示将n1,n2作为参数,n1-n2(最后一行代码)作为返回值,结果与上述是一样的。

高阶函数可以被用于拓展函数中来使得代码更精简,比如apply的用法。我们新建一个StringBuilder的拓展函数:

1
2
3
4
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}

其中fun StringBuilder.build就是对StringBuilder增加了一个拓展函数,然后block: StringBuilder.()中将函数类型定义到StringBuilder类中的好处就是能提供上下文,使得更简单的操作,然后最后的return this就是返回这个定义的StringBuilder对象。然后调用:

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val result = StringBuilder().build {
append("Start eating fruits.\n")
for(fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
}

就实现了类似apply的用法了。

内联函数

在我们定义一个高阶函数时,当编译器转成Java时,会将这个函数创建一个匿名类,意思是每次调用都会创建一个增大开销,而为了避免不必要的开销,就引入了内联函数,只需要在高阶函数前加上inline即可:

1
2
3
4
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1,num2)
return result
}

内联函数的原理时当进行调用时会进行替换,即将n1+n2替换到operation处,然后再将内联函数的代码全部替换到调用的地方。

nonline与crossinline

当高阶函数被加上inline关键字后,里面参数的所有函数类型参数,在调用时均会进行替换,但假如我们某一个函数不想被u内联呢?只需要加上nonline关键字就好了。比如:

1
2
3
inline fun inlineTest(block1: () -> Unit, nonline block2: () -> Unit) {

}

这样就只有block1会被内联了。那为什么有些时候我们不想被内联呢?最主要的是因为内联函数类型参数只允许传递给内联函数,而不能传递给非内联函数,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 定义一个内联函数,带有一个内联的函数类型参数
inline fun inlineFunction1(block: () -> Unit) {
println("Before block execution in inlineFunction1")
block()
println("After block execution in inlineFunction1")
}

// 再定义一个内联函数,带有一个内联的函数类型参数
inline fun inlineFunction2(block: () -> Unit) {
println("Before block execution in inlineFunction2")
block()
println("After block execution in inlineFunction2")
}

// 定义一个非内联函数
fun nonInlineFunction(block: () -> Unit) {
println("Before block execution in nonInlineFunction")
block()
println("After block execution in nonInlineFunction")
}

fun main() {
// 内联函数可以调用另一个内联函数
inlineFunction1 {
inlineFunction2 {
println("Hello from inlineFunction2")
}
}

// 内联函数不能调用非内联函数
// 下面的代码会导致编译错误
// inlineFunction1 {
// nonInlineFunction {
// println("Hello from nonInlineFunction")
// }
// }

// 非内联函数可以调用内联函数和非内联函数
nonInlineFunction {
inlineFunction2 {
println("Hello from inlineFunction2 inside nonInlineFunction")
}
}
}

上面代码就很好的解释了限制性。还有就是内联函数所引用的lambda表达式是可以用return关键字进行函数返回的,而非内联函数只能进行局部返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun printString(str: String, block: (String) -> Unit) {
println("begin")
block(str)
println("end")
}

fun main() {
val str = ""
printString(str) { s ->
print("lambda start")
if(s.isEmpty()) return@printString
println(s)
println("lambda end")
}
println("main end")
}

这里使用了return@printString表示局部返回,不再执行lambda的剩余代码,而内联函数就可以直接使用return,因为他的本质是直接进行替换,会直接返回main()函数,所以编译后也不会执行println("main end")这行代码。所以大部分情况,我们都需要将高阶函数定义成内联函数。

有一种特殊情况:

1
2
3
4
5
6
inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}

这段代码如果没有加inline是可以正常运行的,但如果加了inline的话就会报错:

1
Can't inline 'block' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'block

为什么会报错呢?因为Runnable实际上是一个非内联的函数参数,但是我们将block()这个内联函数作为参数传给了非内联函数,违反了内联函数参数的限制。因为内联函数允许使用return关键字而非内联函数是不允许使用的。那么如何解决呢?只需要加入crossinline关键字就可以了:

1
2
3
4
5
6
inline fun runRunnable(crossinline block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}

crossinline关键字的作用是什么呢,相当于进行了一个约定,保证内联函数的lambda表达式中一定不会出现return关键字,就避免了冲突。这样申明后,在内联函数中仍可使用return@runRunnable进行局部返回,但绝对不能直接使用return