上一期提到了 Lambda,今天趁热打铁,再来撸一题。
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 就直接禁止了。
再来看看下面这段代码,可以正常编译吗?
fun numbers(list: List<Int>) {
list.forEach {
if (it > 2) return
print(it)
}
print("ok")
}
刚才的知识可能会告诉你,不可以编译。
但你又错了,是可以编译的。
foreach 是 Kotlin 标准库中定义的扩展函数。把它和之前我自己定义的 loop 比对一下。
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() 函数其实就等价于下面的代码:
fun numbers(list: List<Int>) {
for (element in list) {
if (element > 2) return
print(element)
}
print("ok")
}
经过内联之后,return 被直接填充到外层函数体中,若执行到这里,直接退出函数,不存在任何歧义。
这么看来,我们被剥夺了直接从 Lambda 表达式中 return 的权利。其实并不然,Kotlin 又提供了另一个奇奇怪怪的语法来实现从 Lambda 中局部返回。
fun numbers(list: List<Int>) {
list.loop {
if (it > 2) return@loop
print(it)
}
print("ok")
}
上面的代码最终会打印 12ok ,实现了仅从 Lambda 表达式中退出。@xxx 默认使用高阶函数名称,你也可以自定义:
fun numbers(list: List<Int>) {
list.loop label@{
if (it > 2) return@label
print(it)
}
print("ok")
}
好像也并没有什么意义。
最后再来个奇奇怪怪的需求,inline 修饰的高阶函数使得 Lambda 表达式中可以直接使用 return 从外部函数中直接退出,但是如果我既想内联,又想禁止这一特性,即不允许 return ,该如何实现呢?
欢迎在评论区交流~