Kotlin学习与实践 (八)集合的函数式 lambda API
与C#、Groovy、Scala等其他支持lambda的语言一样,Kotlin也提供了很多函数式编程风格的集合操作。filter 和 map 函数形成了几个操作的基础,很多几个操作都是借助他们来表达的。
filter、map
首先抛出下面例子说使用的类和集合
data class Person(val name: String, val age: Int) val people = listOf(Person("张三", 20), Person("张er", 32), Person("王五", 50), Person("赵六", 18))
* filter 函数会遍历集合边选出应用给定lambda后会返回true的那些元素
fun testFilter() { val list = listOf(1, 2, 5, 7, 3, 8, 9) println(list.filter { it % 2 == 0 }) //使用filter选出年龄大于30的人。 filter函数可以从集合中移除(筛选掉)你不需要的值,但是它并不会改变这些元素。 println(people.filter { it.age > 30 }) }
* map 函数对集合中的每一个元素应用给定的函数并把结果收集到一个新集合中
fun testMap() { val list = listOf(1, 2, 4, 5) println(list.map { it * it })//使用map 应用规则生成新的 list println(list)//list本身并没有改变 val nameList = people.map { it.name } println("${nameList.javaClass} : $nameList") //class java.util.ArrayList : [张三, 张er, 王五, 赵六] //上面的例子可以用成员引用重写 val nameLists = people.map(Person::name) }
* 可以把filter 、 map 等方法联合起来一起用
fun testFilterMap() { //筛选出年龄大于30的人的名字列表 val olderName = people.filter { it.age > 30 }.map(Person::name) println(olderName) }
* Lambda表达式的代码看起来很简单,但是掩盖了底层操作的复杂性,必须要牢记自己的代码再干什么,如果没有必要就别重复计算
fun testDefect() { //下面这句代码会导致效率降低,因为会遍历到每个人都会进行一次冒泡的maxBy 计算n*n次 people.filter { it.age == people.maxBy(Person::age)!!.age } //下面是优化 val maxBy = people.maxBy(Person::age) people.filter { it.age == maxBy!!.age } }
* lambda可以对map应用过滤和变换函数 键和值分别由各自的函数来处理。
* filterKeys 和 mapKeys过滤和变换map的键,
* filterValues 和 mapValues 过滤和变换map的值。
fun testMapM() { val numbers = mapOf(0 to "Zero", 1 to "One") println(numbers.mapValues { it.value.toUpperCase() }) println(numbers.mapKeys { it.key + 1 }) println(numbers) }
输出结果:
{0=ZERO, 1=ONE}
{1=Zero, 2=One}
{0=Zero, 1=One}
all、any、find
* 另一种常见的任务是检查集合中的所有元素是否都符合条件(或者它的变种,是否存在符合的元素。
* Kotlin中,它们是通过all和any函数表达的。count函数检查有多个元素满足判断式,而find函数返回第一个符合条件的元素。
fun testAllAny() { val canBeInClub17 = { p: Person -> p.age <= 27 } val cannotBeInClub17 = { p: Person -> p.age > 27 } println(people.all(canBeInClub17)) println(people.any(canBeInClub17)) // 注意!all加上某个条件 可以用 any加上这个条件的取反来替换,为了提高代码的可读性建议不在前面加!取反,如下对比第二种可读性更高 println(!people.all(canBeInClub17)) println(people.any(cannotBeInClub17)) // count 方法很容易被遗忘,会选择size,filter会创建中间集合来存储满足判断式的元素。count只是跟踪匹配元素的数量,不关心元素本身,所以会比size高效 println(people.count(canBeInClub17)) // 如果有多个元素满足匹配规则find方法就会返回第一个元素,如果没有则返回null。find还有一个同义方法 firstOrNull,使用这个方法可以更清楚的表达find的意图 println(people.find(canBeInClub17)) println(people.firstOrNull(canBeInClub17)) }
flatMap 和 flatten
* 使用 flatMap 和 flatten 处理嵌套集合中的元素。
* flatMap做了两件事:首先根据作为参数给定函数对集合中的每个元素做变换(映射),然后把多个列表合并(平铺)成一个利列表
class Production(val title: String, val authors: List<String>) fun testFlatMap() { val pros = listOf(Production("pro0", listOf("张三", "李四")) , Production("pro1", listOf("阿三", "阿四")) , Production("pro2", listOf("刘三", "秦四")) , Production("pro3", listOf("司马三", "李四"))) println(pros.map { it.authors }) println(pros.flatMap { it.authors }) //这里使用toSet移除集合中的所有重复元素 println(pros.flatMap { it.authors }.toSet()) // 如果不需要做任何变换,只需要平铺一个集合,可以使用flatten函数 println("flatten ${pros.map { it.authors }.flatten()}") val list = listOf("ab", "abc", "def") println(list.flatMap { it.toList() }) }
sequence
* 链式调用 filter map 等函数的的时候这些函数会及早地创建中间集合,也就是说每一步的中间操作都会产生一个存储数据的临时列表,
* 序列(sequence)就提供了一种另外的操作选择,可以避免这些临时的中间对象
* 惰性集合的入口就是Sequence接口。
* 这个接口表示的就是一个可以逐个列举元素的序列。Sequence 只提供了一个方法:iterator,用来从序列中获取值。
*
* Sequence的强大之处在于其操作的实现方式。序列中的元素求值是惰性的。可以使用序列更高效的执行链式操作,而不需要创建额外的集合来保存过程中产生的中间结果。
*
* 可以调用 asSequence() 扩展函数来把任意集合转换成序列,调用toList来做反向的转换。
fun testSequence() { val pros = listOf(Production("pro0", listOf("张三", "李四")) , Production("pro1", listOf("阿三", "阿四")) , Production("pro2", listOf("刘三", "秦四")) , Production("pro3", listOf("司马三", "李四"))) println(pros.map { it.authors }.filter { it.size > 1 }) //上面这句代码 直接链式调用 map 和 filter 会生成两个中间集合 浪费资源,效率还低 println(pros.asSequence().map(Production::title).filter { it.startsWith("1") }.toList()) }
* 序列操作氛围两类:中间的和末端的。
* 一次中间的操作返回的是另一个序列,这个新序列知道如何变换原始序列中的元素。
* 一次末端操作返回的是一个结果,这个结果可能是集合、元素、数字,或者其他从初始集合的变换序列中获取的任意对象。
* 而且中间操作都是惰性的!
* sequence.map{....}.filter(....).toList() map/filter 都属于中间操作 toList 属于末端操作
fun sequenceOperation() { listOf(1, 2, 9, 4).map { println("map") //这些中间操作都会被立即执行 it * it }.filter { println("filter $it")//这些中间操作都会被立即执行 it > 0 } listOf(1, 2, 9, 4).asSequence().map { println("sequence map")//(惰性)不会立即执行,只有在获取结果的时候才会被执行 it * it }.filter { println("sequence filter $it")//(惰性)不会立即执行,只有在获取结果的时候才会被执行 it > 0 } listOf(1, 2, 9, 4).asSequence().map { println("sequence map")//(惰性)不会立即执行,只有在获取结果的时候才会被执行,末端操作触发了所有的延期计算 it * it }.filter { println("sequence filter $it")//(惰性)不会立即执行,只有在获取结果的时候才会被执行,末端操作触发了所有的延期计算 it > 0 }.toSet()//末端操作触发了所有的延期计算 }
上述代码的执行结果:
map
map
map
map
filter 1
filter 4
filter 81
filter 16
sequence map
sequence filter 1
sequence map
sequence filter 4
sequence map
sequence filter 81
sequence map
sequence filter 16
* 集合和序列的另一个区别是 集合会按照操作函数的顺序 对集合进行函数操作,然后再对结果的每个元素都继续操作下一个函数
* 对序列来说,所有操作是按顺序应用在没一个元素上;处理完第一个元素(map->filter)再去对下一个元素处理,依次类推
*
* 及早求值再整个集合上执行每个操作,惰性求值则逐个处理元素
fun collectionSequenceOrder() { val numbers = listOf(1, 2, 6, 8) var count = 0 var count2 = 0 numbers.map { count++ println("collection map 执行了 $count 次") it * it }.find { it > 4 } //count 最后会等于4,map函数会执行四次 numbers.asSequence().map { count2++ println("asSequence map 执行了 $count2 次") it * it }.find { it > 4 }//count2 最后会等于3,,map函数会执行三次 }