Kotlin 用高阶函数处理集合数据
前言
一个函数 f(x) 的接收参数是另一个函数(g(x))或者它的返回值是另一个函数(h(x)),在 Kotlin 中 f(x)就是一个高阶函数。Kotlin 标准库中为开发者提供了丰富的高阶函数,有sumBy
, reduce
, fold
, map
,filter
,forEach
等。本篇文章就来学习这些高阶函数的使用。
本文概览
过滤集合 filter
场景:输入一个账户列表List<Account>
,返回资产小于 100 的账户列表:
Java:
Kotlin 可以使用filter
函数:
filter
的逻辑是,新建一个空的 ArrayList(),然后把 lambda 返回符合条件的元素加入到列表。
遍历求和 sumBy
场景:输入一个账户列表List<Account>
,求所有账户的财产总和sum
。
Java:
Kotlin 使用高阶函数 sumBy
函数:
究竟sumBy
做了什么?查看源码可知,它做的事情和上面 Java 实现的getAccountsSum
是一样的,元素值是通过传入的 lambda 来计算,而不是写死的Account.value
。
通过传入函数来完成函数功能的函数,被称为高阶函数,高阶函数也因此具有很高的通用性和复用效率。
不仅传入函数作为参数的函数被称为高阶函数,返回值为函数的函数也同样被称为高阶函数。
遍历求值 reduce
sumBy
有局限性,只能求和,且只接受Int
和Double
两种类型的值。若需要计算其他类型或者更复杂的情况呢?
场景:输入一个列表List<Int>
,返回它们全部相乘的结果。
Java:
Kotlin 可以使用reduce
函数:
reduce
的处理:将初始值acc
设置为集合的第一个值,然后从第二个值开始,依次执行acc = lambda(acc, v)
,遍历完后返回acc
。
reduce
不仅限做加法运算,比sumBy
更具有通用性。
值得注意一点:如果集合为空,
reduce
会抛出 UnsupportedOperationException("Empty collection can't be reduced.")
遍历求值 fold 函数
上面sumBy
函数场景和reduce
函数场景用的是不同的数据结构。acc
会被初始化为集合的第一个元素,所以reduce
函数的输出也被限制为集合的范型类型。也就是说,sumBy
无法用reduce
代替。
Kotlin 有没有能指定acc
类型的高阶函数?是有的,叫fold
函数。
再回到sumBy
的场景:输入一个账户列表List<Account>
,求这些账户的财产总和sum
:
fold
比reduce
多了一个参数——初始值,用来赋值给acc
。所以可以通过这个参数来指定acc
的类型。这样一来,fold
可以替代sumBy
的场景。
fold
还有另一点好:acc
由传入参数初始化,所以没有集合不能为空的限制。大部分情况下,建议使用fold
来代替reduce
。
列表返回列表 map 函数
业务场景:输入账户列表List<Account>
,找到所有资产大于 10000 的账户,封装成 VIP 列表返回:
Java:
Kotlin 可以通过filter
函数加map
函数完成:
先用filter
函数筛选出符合条件的账户,然后用map
函数将过滤后的每一个账户转换为VipAccount
。map
的逻辑较为简单,返回一个和调用者相同大小的列表,具体元素值为 lambda 的执行结果。
用 forEach 代替 for
在部分循环遍历场景,不妨试试forEach
代替传统的 for
循环,会简洁很多。
什么?还想要 index 下标?
其实不仅forEach
有下标版本forEachIndexed
,几乎所有高阶函数都有对应的 Indexed 版本。可以尝试调取一下。Kotlin 官方提供了数十个高阶函数,但其实掌握了以上几个常用高阶函数,基本可以适用场景了。
担心性能问题?
可能会担心,如此频繁的声明 lambda,会不会使得类的数量膨胀?官方提供的高阶函数,其实都是用inline
关键字修饰的。意味着不仅高阶函数的调用会被函数的实际代码代替,而且声明的 lambda 也会被解析成具体的代码,而不是方法调用。Kotlin 高阶函数用 inline 关键字修饰,所以 lambda 不会生成新的 jvm class。在声明自己的高阶函数时,也建议用inline
关键字修饰,防止类数量暴增。
还有一点,像map
,filter
这样返回列表的高阶函数,每一次操作都会产生一个列表,这会不会增加垃圾回收的压力?答案是会的。但如果数据量不是万级别的,操作频率不是毫秒级别的,对性能的影响其实是很小的,是在移动端的场景更是难以遇到。但了解高阶函数对性能开销是很有必要的,可以避免一些耗性能的操作。
版权声明: 本文为 InfoQ 作者【子不语Any】的原创文章。
原文链接:【http://xie.infoq.cn/article/9a9681a2a916a228529aca8c3】。文章转载请联系作者。
评论