局部函数
您可以在任何地方定义函数,甚至可以在其他函数内部定义函数。
在其他函数内部定义的命名函数称为本地函数。本地函数通过提取重复的代码来减少重复,并且仅在周围函数内部可见,因此它们不会“污染您的命名空间”。在下面的示例中,即使 log()
被定义得就像任何其他函数一样,它也是嵌套在 main()
内部的:
// LocalFunctions/LocalFunctions.kt
import atomictest.eq
fun main() {
val logMsg = StringBuilder()
fun log(message: String) =
logMsg.appendLine(message)
log("Starting computation")
val x = 42 // 模拟计算
log("Computation result: $x")
logMsg.toString() eq """
Starting computation
Computation result: 42
"""
}
本地函数是闭包:它们捕获来自周围环境的 var
或 val
,否则必须作为附加参数传递。log()
使用了在其外部作用域中定义的 logMsg
。这样,您就不需要将 logMsg
反复传递给 log()
。
您可以创建本地扩展函数:
// LocalFunctions/LocalExtensions.kt
import atomictest.eq
fun main() {
fun String.exclaim() = "$this!"
"Hello".exclaim() eq "Hello!"
"Hallo".exclaim() eq "Hallo!"
"Bonjour".exclaim() eq "Bonjour!"
"Ciao".exclaim() eq "Ciao!"
}
exclaim()
仅在 main()
内部可用。
以下是一个演示类和用于此章节的示例值:
// LocalFunctions/Session.kt
package localfunctions
class Session(
val title: String,
val speaker: String
)
val sessions = listOf(Session(
"Kotlin Coroutines", "Roman Elizarov"))
val favoriteSpeakers = setOf("Roman Elizarov")
您可以使用函数引用引用本地函数:
// LocalFunctions/LocalFunctionReference.kt
import localfunctions.*
import atomictest.eq
fun main() {
fun interesting(session: Session): Boolean {
if (session.title.contains("Kotlin") &&
session.speaker in favoriteSpeakers) {
return true
}
// ... 更多检查
return false
}
sessions.any(::interesting) eq true
}
interesting()
仅使用一次,因此我们可能倾向于将其定义为 lambda。正如您将在本章后面看到的那样,在 interesting()
中的 return
表达式会使将其转换为 lambda 变得复杂。我们可以通过匿名函数来避免这种复杂性。与本地函数类似,匿名函数在其他函数内部定义,但匿名函数没有名称。匿名函数在概念上类似于 lambda,但使用 fun
关键字。以下是使用匿名函数重写的 LocalFunctionReference.kt
:
// LocalFunctions/InterestingSessions.kt
import localfunctions.*
import atomictest.eq
fun main() {
sessions.any(
fun(session: Session): Boolean { // [1]
if (session.title.contains("Kotlin") &&
session.speaker in favoriteSpeakers) {
return true
}
// ... 更多检查
return false
}) eq true
}
- [1] 匿名函数看起来像一个没有函数名的常规函数。在这里,匿名函数作为参数传递给
sessions.any()
。
如果 lambda 变得过于复杂并且难以阅读,可以将其替换为本地函数或匿名函数。
标签
在这里,forEach()
执行包含 return
的 lambda:
// LocalFunctions/ReturnFromFun.kt
import atomictest.eq
fun main() {
val list = listOf(1, 2, 3, 4, 5)
val value = 3
var result = ""
list.forEach {
result += "$it"
if (it == value) {
result eq "123"
return // [1]
}
}
result eq "永远不会到达这里" // [2]
}
return
表达式会从使用 fun
定义的函数中退出(即不是 lambda)。在第 [1] 行,这意味着从 main()
返回。第 [2] 行永远不会被调用,因此没有输出。
要仅从 lambda 返回,而不是从周围的函数返回,可以使用标记的 return
:
// LocalFunctions/LabeledReturn.kt
import atomictest.eq
fun main() {
val list = listOf(1, 2, 3, 4, 5)
val value = 3
var result = ""
list.forEach {
result += "$it"
if (it == value) return@forEach
}
result eq "12345"
}
这里,标签是调用 lambda 的函数的名称。标记的返回表达式 return@forEach
告诉它仅返回到名称 forEach
。
可以通过在 lambda 前面添加 label@
来创建标签,其中 label
可以是任何名称:
// LocalFunctions/CustomLabel.kt
import atomictest.eq
fun main() {
val list = listOf(1, 2, 3, 4, 5)
val value = 3
var result = ""
list.forEach tag@{ // [1]
result
+= "$it"
if (it == value) return@tag // [2]
}
result eq "12345"
}
- [1] 此 lambda 被标记为
tag
。 - [2]
return@tag
从 lambda 返回,而不是从main()
返回。
让我们将 InterestingSessions.kt
中的匿名函数替换为 lambda:
// LocalFunctions/ReturnInsideLambda.kt
import localfunctions.*
import atomictest.eq
fun main() {
sessions.any { session ->
if (session.title.contains("Kotlin") &&
session.speaker in favoriteSpeakers) {
return@any true
}
// ... 更多检查
false
} eq true
}
我们必须返回到一个标签,以便仅退出 lambda,而不是退出 main()
。
操纵本地函数
您可以将 lambda 或匿名函数存储在 var
或 val
中,然后使用该标识符调用函数。要存储本地函数,请使用函数引用(参见成员引用)。
在下面的示例中,first()
创建一个匿名函数,second()
使用 lambda,third()
返回对本地函数的引用。fourth()
通过使用更紧凑的表达式主体实现了与 third()
相同的效果。fifth()
使用 lambda 实现相同的效果:
// LocalFunctions/ReturningFunc.kt
package localfunctions
import atomictest.eq
fun first(): (Int) -> Int {
val func = fun(i: Int) = i + 1
func(1) eq 2
return func
}
fun second(): (String) -> String {
val func2 = { s: String -> "$s!" }
func2("abc") eq "abc!"
return func2
}
fun third(): () -> String {
fun greet() = "Hi!"
return ::greet
}
fun fourth() = fun() = "Hi!"
fun fifth() = { "Hi!" }
fun main() {
val funRef1: (Int) -> Int = first()
val funRef2: (String) -> String = second()
val funRef3: () -> String = third()
val funRef4: () -> String = fourth()
val funRef5: () -> String = fifth()
funRef1(42) eq 43
funRef2("xyz") eq "xyz!"
funRef3() eq "Hi!"
funRef4() eq "Hi!"
funRef5() eq "Hi!"
first()(42) eq 43
second()("xyz") eq "xyz!"
third()() eq "Hi!"
fourth()() eq "Hi!"
fifth()() eq "Hi!"
}
main()
首先验证调用每个函数是否确实返回了预期类型的函数引用。然后,使用适当的参数调用每个 funRef
。最后,调用每个函数,然后立即通过添加适当的参数列表调用返回的函数引用。例如,调用 first()
返回一个函数,因此我们通过添加参数列表 (42)
来调用 该 函数。
练习和解答可以在 www.AtomicKotlin.com 找到。