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

Lambdas(Lambda 表达式)

Lambda 表达式生成更简洁、更易于理解的代码。

Lambda(也称为函数字面值)是一个低仪式性的函数:它没有名称,需要最少的代码来创建,并且您可以将其直接插入到其他代码中。

首先,考虑一下 map(),它适用于像 List 这样的集合。map() 的参数是一个转换函数,该函数应用于集合中的每个元素。map() 返回一个包含所有转换后元素的新的 List。在这里,我们将每个 List 项转换为一个用 [] 括起来的 String

// Lambdas/BasicLambda.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ n: Int -> "[$n]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

Lambda 是初始化 result 时大括号内的代码。参数列表与函数体由箭头 ->(与 when 表达式中使用的箭头相同)分隔。

函数体可以是一个或多个表达式。最后一个表达式成为 lambda 的返回值。

BasicLambda.kt 显示了完整的 lambda 语法,但通常可以简化。我们通常会在需要时创建和使用 lambda,这意味着 Kotlin 通常可以推断出类型信息。在这里,n 的类型是被推断出来的:

// Lambdas/LambdaTypeInference.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ n -> "[$n]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

Kotlin 可以判断出 n 是一个 Int,因为 lambda 正在与 List<Int> 一起使用。

如果只有一个参数,Kotlin 会为该参数生成名为 it 的名称,这意味着我们不再需要 n ->

// Lambdas/LambdaIt.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ "[$it]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

map() 可以处理任何类型的 List。在这里,Kotlin 推断出 lambda 参数 it 的类型为 Char

// Lambdas/Mapping.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  val result =
    list.map({ "[${it.toUpperCase()}]" })
  result eq listOf("[A]", "[B]", "[C]", "[D]")
}

如果 lambda 是唯一的函数参数,或者是最后一个参数,您可以省略大括号周围的括号,从而产生更简洁的语法:

// Lambdas/OmittingParentheses.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  val result =
    list.map { "[${it.toUpperCase()}]" }
  result eq listOf("[A]", "[B]", "[C]", "[D]")
}

如果函数接受多个参数,则除最后一个 lambda 参数外,所有参数都必须位于括号内。例如,您可以将 joinToString() 的最后一个参数指定为 lambda。该 lambda 用于将每个元素转换为 String,然后将所有元素连接起来:

// Lambdas/JoinToString.kt
import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(" ") { "[$it]" } eq
    "[9] [11] [23] [32]"
}

如果要将 lambda 提供为命名参数,必须将 lambda 放在参数列表的括号内:

// Lambdas/LambdaAndNamedArgs.kt
import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(
    separator = " ",
    transform = { "[$it]" }
  ) eq "[9] [11] [23] [32]"
}

以下是带有多个参数的 lambda 的语法:

// Lambdas/TwoArgLambda.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, element ->
    "[$index: $element]"
  } eq listOf("[0: a]", "[1: b]", "[2: c]")
}

这使用了 mapIndexed() 库函数,它将 list 中的每个元素与该元素的索引一起传递。我们在 mapIndexed() 之后应用的 lambda 需要两个参数,以匹配索引和元素(在 List<Char> 的情况下,元素是一个字符)。

如果您不使用特定的参数,您可以使用下划线来忽略它,以消除关于未使用标识符的编译器警告:

// Lambdas/Underscore.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, _ ->
    "[$index]"
  } eq listOf("[0]", "[1]", "[2]")
}

请注意,可以使用 list.indices 重写 Underscore.kt

// Lambdas/ListIndicesMap.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.indices.map {
    "[$it]"
  } eq listOf("[0]", "[1]", "[2]")
}

Lambda 可以具有零个参数,此时您可以保留箭头以强调,但 Kotlin 风格指南建议省略箭头:

// Lambdas/ZeroArguments

.kt
import atomictest.*

fun main() {
  run { -> trace("A Lambda") }
  run { trace("Without args") }
  trace eq """
    A Lambda
    Without args
  """
}

标准库的 run() 函数只是调用其 lambda 参数。

  • -

您可以在任何使用普通函数的地方使用 lambda,但如果 lambda 变得过于复杂,通常最好定义一个命名函数,以便清晰明了,即使您只打算使用一次。

练习和解答可在 www.AtomicKotlin.com 找到。