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的重要性

Lambdas可能看起来只是语法糖,但它们为你的编程提供了重要的能力。

代码经常会操作集合的内容,并且通常会以轻微的修改重复执行这些操作。考虑从集合中选择元素,例如年龄在给定值以下的人、具有特定角色的员工、特定城市的居民或未完成的订单。以下是一个从列表中选择偶数的示例。假设我们没有一个丰富的用于处理集合的函数库 - 我们将不得不实现自己的filterEven()操作:

// ImportanceOfLambdas/FilterEven.kt
package importanceoflambdas
import atomictest.eq

fun filterEven(nums: List<Int>): List<Int> {
  val result = mutableListOf<Int>()
  for (i in nums) {
    if (i % 2 == 0) {    // [1]
      result += i
    }
  }
  return result
}

fun main() {
  filterEven(listOf(1, 2, 3, 4)) eq
    listOf(2, 4)
}

如果一个元素除以2的余数为0,它将附加到结果中。

想象一下,你需要类似的操作,但是针对大于2的数字。你可以复制filterEven()并修改选择包含在结果中的元素的小部分:

// ImportanceOfLambdas/GreaterThan2.kt
package importanceoflambdas
import atomictest.eq

fun greaterThan2(nums: List<Int>): List<Int> {
  val result = mutableListOf<Int>()
  for (i in nums) {
    if (i > 2) {         // [1]
      result += i
    }
  }
  return result
}

fun main() {
  greaterThan2(listOf(1, 2, 3, 4)) eq
    listOf(3, 4)
}

前两个示例之间唯一显著的区别是指定所需元素的代码行(在两种情况下均为**[1]**)。

使用Lambda,我们可以在两种情况下使用相同的函数。标准库函数filter()接受一个谓词,指定要保留的元素,这个谓词可以是Lambda:

// ImportanceOfLambdas/Filter.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val even = list.filter { it % 2 == 0 }
  val greaterThan2 = list.filter { it > 2 }
  even eq listOf(2, 4)
  greaterThan2 eq listOf(3, 4)
}

现在,我们有了清晰、简洁的代码,避免了重复。evengreaterThan2都使用了filter(),只有谓词不同。filter()经过了大量测试,因此不太可能引入错误。

注意,filter()处理了否则需要手写代码的迭代。尽管自己管理迭代可能不会显得很费力,但这是一个更容易出错的细节,也是一个更容易出错的地方。因为它们非常“显而易见”,所以这些错误尤其难以发现。

这是函数式编程的一个标志,其中map()filter()就是示例。函数式编程通过一步步解决问题。这些函数通常执行看起来微不足道的操作 - 编写自己的代码而不是使用map()filter()并不难。然而,一旦你拥有了这些小而经过调试的解决方案的集合,你可以在不在每个级别都进行调试的情况下轻松地将它们组合起来。这使你能够更快地创建更加健壮的代码。

你可以将Lambda存储在varval中。这允许通过将其作为参数传递给不同的函数来重用该Lambda的逻辑:

// ImportanceOfLambdas/StoringLambda.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val isEven = { e: Int -> e % 2 == 0 }
  list.filter(isEven) eq listOf(2, 4)
  list.any(isEven) eq true
}

isEven检查一个数字是否为偶数,并将此引用作为参数传递给filter()any()。在定义isEven时,我们必须指定参数类型,因为没有类型推断的上下文。

Lambda的另一个重要特性是能够引用其作用域之外的元素。当一个函数在其环境中“闭合”或“捕获”元素时,我们将其称为闭包。不幸的是,一些语言将“闭包”一词与Lambda的概念混为一谈。这两个概念完全不同:你可以有没有闭包的Lambda,也可以有没有Lambda的闭包。

当一种语言支持闭包时,它会以你期望的方式“正常工作”:

// ImportanceOfLambdas/Closures.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  val divider = 5
  list.filter { it % divider == 0 } eq
    listOf(5, 10)
}

在这里,Lambda“捕获”了在Lambda之外定义的val divider。Lambda不仅可以读取捕获的元素,还可以修改它们:

// ImportanceOfLambdas/Closures2.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  var sum = 0
  val divider = 5
  list.filter { it % divider == 0 }
    .forEach {

 sum += it }
  sum eq 15
}

forEach()库函数将指定的操作应用于集合的每个元素。

尽管你可以像Closures2.kt中那样捕获可变变量sum,但通常你可以改变代码,避免修改环境的状态:

// ImportanceOfLambdas/Sum.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  val divider = 5
  list.filter { it % divider == 0 }
    .sum() eq 15
}

sum()适用于数字列表,将列表中的所有元素相加。

普通函数也可以闭合周围的元素:

// ImportanceOfLambdas/FunctionClosure.kt
package importanceoflambdas
import atomictest.eq

var x = 100

fun useX() {
  x++
}

fun main() {
  useX()
  x eq 101
}

useX()从其环境中捕获并修改x

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