Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

局部函数

您可以在任何地方定义函数,甚至可以在其他函数内部定义函数。

在其他函数内部定义的命名函数称为本地函数。本地函数通过提取重复的代码来减少重复,并且仅在周围函数内部可见,因此它们不会“污染您的命名空间”。在下面的示例中,即使 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
  """
}

本地函数是闭包:它们捕获来自周围环境的 varval,否则必须作为附加参数传递。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 或匿名函数存储在 varval 中,然后使用该标识符调用函数。要存储本地函数,请使用函数引用(参见成员引用)。

在下面的示例中,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 找到。