Kotlin_高阶函数详解
定义高阶函数
高阶函数: 如果一个函数接收另一个函数作为参数, 或者返回值的类型是另一个函数, 那么该函数就被称为高阶函数
高阶函数用法
// 左边是接收参数, 右边是返回参数, Unit相对于Java中的void
(String, Int) -> Unit
fun example(func: (String, Int) -> Unit){
func("hello", 123)
}
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}
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 result1 = num1AndNum2(num1, num2, ::plus)
val result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
//val result1 = num1AndNum2(num1, num2, ::minus)
val result2 = num1AndNum2(num1, num2) { n1, n2 ->
n1 - n2
}
println("result1 is $result1")
println("result2 is $result2")
}
高阶函数模仿实现apply
- 定义高阶函数完整的语法规则, 在函数类型的前面加上ClassName, 就表示这个函数类型是定义在哪个类中
- 定义到StringBuilder类中的好处是当我们调用build函数时传入的Lambda表达式会自动拥有StringBuilder的上下文, 同时这也是apply函数的实现方式
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
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())
内联函数的作用
高阶函数的实现原理
Kotlin代码最终还是要编译成Java字节码, 但Java中并没有高阶函数.
Kotlin:
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(大致转换为):
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表达式, 都会创建一个新的匿名类实例, 会造成额外的内存和性能开销.
内联函数
内联函数能完全消除Lambda表达式所带来的运行时开销
原始:
inline 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 result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
}
第一次替换:
将Lambda表达式中的代码替换到函数类型参数调用的地方
inline fun num1AndNum2(num1: Int, num2: Int): Int {
val result = num1+num2;
return result
}
fun main()
{
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1, num2)
}
第二次替换:
将内联函数中的全部代码替换到函数调用的地方
fun main()
{
val num1 = 100
val num2 = 80
val result1 = num1+num2
}
noinline和crossinline
noinline
一个高阶函数如果接受了两个或者更多函数类型的参数, 这时给函数加上inline关键字, Kotlin编译器会自动将所有引用的Lambda表达式全部内联.
但如果只想内联其中一个Lambda表达式, 代码如下
// 只会对block1参数所引用的Lambda表达式进行内联
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit){
}
- 内联的函数类型参数在编译的时候会被进行代码替换, 它没有真正的参数属性.
- 非内联的函数类型参数可以自由地传递给其他任何函数, 因为它就是一个真实的参数, 而内联的函数类型参数只允许传递给另外一个内联函数, 这也是它最大的局限性.
- 内联函数所引用的Lambda表达式中可以使用return关键字来进行函数返回, 而非内联函数只能进行局部返回.
非内联函数局部返回
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
*/
内联函数返回
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
*/
使用内联函数可能会有的错误
inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
// 报错
block()
}
runnable.run()
}
书中解释:
这个错误出现的原因解释起来可能会稍微有点复杂。首先,在runRunnable()函数中,我们创建了一个Runnable对象,并在Runnable的Lambda表达式中调用了传入的函数类型参数。而Lambda表达式在编译的时候会被转换成匿名类的实现方式,也就是说,上述代码实际上是在匿名类中调用了传入的函数类型参数。而内联函数所引用的Lambda表达式允许使用return关键字进行函数返回,但是由于我们是在匿名类中调用的函数类型参数,此时是不可能进行外层调用函数返回的,最多只能对匿名类中的函数调用进行返回,因此这里就提示了上述错误。也就是说,如果我们在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误.
inline fun runRunnable(crossinline block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
书中解释:
因为内联函数的Lambda表达式中允许使用return关键字,和高阶函数的匿名类实现中不允许使用return关键字之间造成了冲突。而crossinline关键字就像一个契约,它用保证在内联函数的Lambda表达式中一定不会使用return关键字,这样冲突就不存在了,问题也就巧妙地解决了。声明了crossinline之后,我们就无法在调用runRunnable函数时的Lambda表达式中使用return关键字进行函数返回了,但是仍然可以使用return@runRunnable的写法进行局部返
回。总体来说,除了在return关键字的使用上有所区别之外,crossinline保留了内联函数的其他所有特性。