上一期提到了 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
比对一下。
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 中局部返回。
代码语言:javascript复制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
,该如何实现呢?
欢迎在评论区交流~