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)
}
现在,我们有了清晰、简洁的代码,避免了重复。even
和greaterThan2
都使用了filter()
,只有谓词不同。filter()
经过了大量测试,因此不太可能引入错误。
注意,filter()
处理了否则需要手写代码的迭代。尽管自己管理迭代可能不会显得很费力,但这是一个更容易出错的细节,也是一个更容易出错的地方。因为它们非常“显而易见”,所以这些错误尤其难以发现。
这是函数式编程的一个标志,其中map()
和filter()
就是示例。函数式编程通过一步步解决问题。这些函数通常执行看起来微不足道的操作 - 编写自己的代码而不是使用map()
和filter()
并不难。然而,一旦你拥有了这些小而经过调试的解决方案的集合,你可以在不在每个级别都进行调试的情况下轻松地将它们组合起来。这使你能够更快地创建更加健壮的代码。
你可以将Lambda存储在var
或val
中。这允许通过将其作为参数传递给不同的函数来重用该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 找到。