5.5.2 返回与跳转

原文链接: https://kotlinlang.org/docs/returns.html

5.5.2 返回与跳转

Kotlin 有三个结构化跳转表达式:

  • return 默认从最近的外层函数或匿名函数返回。
  • break 终止最近的循环。
  • continue 进入最近循环的下一步。

这些表达式都可以作为更大表达式的一部分使用:

1
val s = person.name ?: return

这些表达式的类型是 Nothing 类型。

break 与 continue 标签

Kotlin 中的任何表达式都可以用标签(label)标记。标签的形式是标识符后跟 @ 符号,例如 abc@ 或 fooBar@。要给表达式加标签,只需在它前面添加一个标签。

1
2
3
loop@ for (i in 1..100) {
    // ...
}

现在,你可以在 break 或 continue 后面加上标签:

1
2
3
4
5
loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop
    }
}

带标签的 break 会跳转到标有该标签的循环之后的执行点;continue 则进入该循环的下一次迭代。

注意: 在某些情况下,你可以非局部地使用 break 和 continue,而无需显式定义标签。这类非局部用法在外层内联函数中的 lambda 表达式里是合法的。

返回到标签

在 Kotlin 中,函数可以通过函数字面量、局部函数和对象表达式进行嵌套。带限定符的 return 允许你从外层函数返回。

最重要的使用场景是从 lambda 表达式返回。要从 lambda 表达式返回,请给它加上标签并限定 return:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 局部返回到 lambda 的调用方 —— 即 forEach 循环
        print(it)
    }
    print(" done with explicit label")
}

fun main() {
    foo()
}

这样,它就只从 lambda 表达式返回。使用隐式标签通常更方便,因为这种标签与被传入 lambda 的函数同名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // 局部返回到 lambda 的调用方 —— 即 forEach 循环
        print(it)
    }
    print(" done with implicit label")
}

fun main() {
    foo()
}

或者,你可以用匿名函数替代 lambda 表达式。匿名函数中的 return 语句会从该匿名函数本身返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return // 局部返回到匿名函数的调用方 —— 即 forEach 循环
        print(value)
    })
    print(" done with anonymous function")
}

fun main() {
    foo()
}

注意,前面三个示例中局部返回的用法与普通循环中 continue 的用法类似。

break 没有直接对应的写法,但可以通过添加一个外层 run lambda 并从中非局部返回来模拟:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // 从传给 run 的 lambda 中非局部返回
            print(it)
        }
    }
    print(" done with nested loop")
}

fun main() {
    foo()
}

这里的非局部返回之所以可行,是因为嵌套的 forEach() lambda 相当于一个内联函数。

在返回值时,解析器优先把它当作带限定符的返回:

1
return@a 1

它的含义是"在标签 @a 处返回 1",而不是"返回一个带标签的表达式 (@a 1)"。

注意: 在某些情况下,你可以不使用标签就从 lambda 表达式返回。这类非局部返回位于 lambda 中,但会退出外层的内联函数。