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

  1. 定义高阶函数完整的语法规则, 在函数类型的前面加上ClassName, 就表示这个函数类型是定义在哪个类中
  2. 定义到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){

}
  1. 内联的函数类型参数在编译的时候会被进行代码替换, 它没有真正的参数属性.
  2. 非内联的函数类型参数可以自由地传递给其他任何函数, 因为它就是一个真实的参数, 而内联的函数类型参数只允许传递给另外一个内联函数, 这也是它最大的局限性.
  3. 内联函数所引用的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保留了内联函数的其他所有特性。

参考

郭霖. 《第一行代码 Android 第3版》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_cen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值