让你迷惑的 Kotlin 代码(3)

2021-08-31 15:43:06 浏览数 (1)

上一期提到了 Lambda,今天趁热打铁,再来撸一题。

代码语言:javascript复制
fun <T> Iterable<T>.loop(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

fun numbers(list: List<Int>) {
    list.loop {
        if (it > 2) return
        print(it)
    }
    print("ok")
}

fun main(args: Array<String>) {
    numbers(listOf(1, 2, 3))
}

老规矩,猜答案。

分析一下题目。

loop() 函数的参数是函数类型,我们一般称这种参数或者返回值是函数的函数为 高阶函数loop() 函数会遍历 Iterable 的每个元素,并执行指定操作。

numbers() 函数会遍历集合 list,并且当 it > 2 的时候调用 return ,最后再打印 ok

所以问题的关键在于传入 loop 函数的 Lambda 中的 return 到底是从哪里返回?

如果是从 Lambda 返回到外层函数的话,会打印 12ok选 B

如果是从外层函数直接返回的话,会打印 12选 D

那么,答案是哪个 ?

...

...

...

答案是 A,无法编译。

不信的话,可以 CV 到 IDE 中,确实是无法编译的。Kotlin 不允许在 Lambda 表达式中这样直接使用 return 。为什么呢?个人猜测正是因为可能存在 究竟是返回到哪里 的语义不确定性,Kotlin 就直接禁止了。

再来看看下面这段代码,可以正常编译吗?

代码语言:javascript复制
fun numbers(list: List<Int>) {
    list.forEach {
        if (it > 2) return
        print(it)
    }
    print("ok")
}

刚才的知识可能会告诉你,不可以编译。

但你又错了,是可以编译的。

foreach 是 Kotlin 标准库中定义的扩展函数。把它和之前我自己定义的 loop 比对一下。

代码语言:javascript复制
fun <T> Iterable<T>.loop(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

唯一的区别是 foreach() 函数是用 inline 修饰的,它是一个内联函数。关于 inline,我写过一篇文章, 重学 Kotlin —— inline,包治百病的性能良药?。

为什么使用 inline 修饰的高阶函数中的 Lambda 表达式中可以使用 return 呢?

因为这种情况下没有语义上的歧义。内联函数会直接将函数代码 “复制” 到函数调用处,foreach 版本的 numbers() 函数其实就等价于下面的代码:

代码语言:javascript复制
fun numbers(list: List<Int>) {
    for (element in list) {
        if (element > 2) return
        print(element)
    }
    print("ok")
}

经过内联之后,return 被直接填充到外层函数体中,若执行到这里,直接退出函数,不存在任何歧义。

这么看来,我们被剥夺了直接从 Lambda 表达式中 return 的权利。其实并不然,Kotlin 又提供了另一个奇奇怪怪的语法来实现从 Lambda 中局部返回。

代码语言:javascript复制
fun numbers(list: List<Int>) {
    list.loop {
        if (it > 2) return@loop
        print(it)
    }
    print("ok")
}

上面的代码最终会打印 12ok ,实现了仅从 Lambda 表达式中退出。@xxx 默认使用高阶函数名称,你也可以自定义:

代码语言:javascript复制
fun numbers(list: List<Int>) {
    list.loop label@{
        if (it > 2) return@label
        print(it)
    }
    print("ok")
}

好像也并没有什么意义。

最后再来个奇奇怪怪的需求,inline 修饰的高阶函数使得 Lambda 表达式中可以直接使用 return 从外部函数中直接退出,但是如果我既想内联,又想禁止这一特性,即不允许 return ,该如何实现呢?

欢迎在评论区交流~

0 人点赞