写点什么

Kotlin 用高阶函数处理集合数据

作者:子不语Any
  • 2022-11-30
    湖南
  • 本文字数:2255 字

    阅读完需:约 7 分钟

Kotlin用高阶函数处理集合数据

前言

一个函数 f(x) 的接收参数是另一个函数(g(x))或者它的返回值是另一个函数(h(x)),在 Kotlin 中 f(x)就是一个高阶函数。Kotlin 标准库中为开发者提供了丰富的高阶函数,有sumBy, reduce, fold, mapfilterforEach等。本篇文章就来学习这些高阶函数的使用。

本文概览

过滤集合 filter

场景:输入一个账户列表List<Account>,返回资产小于 100 的账户列表:


Java:


public List<Account> getPoorAccounts(List<Account> accounts) {    List<Account> qbAccounts = new ArrayList<>(); // 声明存放目标元素的列表    for (Account a: accounts) {        if (a.value < 100) {            qbAccounts.add(a);        }    }    return qbAccounts;}
复制代码


Kotlin 可以使用filter函数:


val qbAccounts = accounts.filter { it.value < 100 }
复制代码


filter的逻辑是,新建一个空的 ArrayList(),然后把 lambda 返回符合条件的元素加入到列表。

遍历求和 sumBy

场景:输入一个账户列表List<Account>,求所有账户的财产总和sum


Java:


public int getAccountsSum(List<Account> accounts) {    int sum = 0;    for (Account a: accounts) { // for循环遍历        sum += a.value;    }    return sum;}
复制代码


Kotlin 使用高阶函数 sumBy函数:


val sum = accounts.sumBy { it.value }
复制代码


究竟sumBy做了什么?查看源码可知,它做的事情和上面 Java 实现的getAccountsSum是一样的,元素值是通过传入的 lambda 来计算,而不是写死的Account.value


通过传入函数来完成函数功能的函数,被称为高阶函数,高阶函数也因此具有很高的通用性和复用效率。


不仅传入函数作为参数的函数被称为高阶函数,返回值为函数的函数也同样被称为高阶函数。

遍历求值 reduce

sumBy有局限性,只能求和,且只接受IntDouble两种类型的值。若需要计算其他类型或者更复杂的情况呢?


场景:输入一个列表List<Int>,返回它们全部相乘的结果。


Java:


public int getResult(List<int> values) {    int result = 0;    for (Account a: accounts) {        result *= values;    }    return result;}
复制代码


Kotlin 可以使用reduce函数:


val result = values.reduce { acc, v -> acc * v }
复制代码


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


val result = accounts.fold(0) { acc, v -> acc + v.value }
复制代码


foldreduce多了一个参数——初始值,用来赋值给acc。所以可以通过这个参数来指定acc的类型。这样一来,fold可以替代sumBy的场景。


fold还有另一点好:acc由传入参数初始化,所以没有集合不能为空的限制。大部分情况下,建议使用fold来代替reduce

列表返回列表 map 函数

业务场景:输入账户列表List<Account>,找到所有资产大于 10000 的账户,封装成 VIP 列表返回:


Java:


public List<VipAccount> getVipAccounts(List<Account> accounts) {    List<VipAccount> vipAccounts = new ArrayList<>();// 声明VIP列表    for (Account a: accounts) {        if (a.value >= 10000) {            vipAccounts.add(new VipAccount(a));// 插入元素        }    }    return vipAccounts;}
复制代码


Kotlin 可以通过filter函数加map函数完成:


val vipAccounts = accounts        .filter { it.value >= 10000 } // 过滤条件        .map { VipAccount(it) }
复制代码


先用filter函数筛选出符合条件的账户,然后用map函数将过滤后的每一个账户转换为VipAccountmap的逻辑较为简单,返回一个和调用者相同大小的列表,具体元素值为 lambda 的执行结果。

用 forEach 代替 for

在部分循环遍历场景,不妨试试forEach代替传统的 for 循环,会简洁很多。


accounts.forEach {    println("account: $it")}
复制代码


什么?还想要 index 下标?


accounts.forEachIndexed { index, account ->    println("index: $index")    println("account: $account")}
复制代码


其实不仅forEach有下标版本forEachIndexed,几乎所有高阶函数都有对应的 Indexed 版本。可以尝试调取一下。Kotlin 官方提供了数十个高阶函数,但其实掌握了以上几个常用高阶函数,基本可以适用场景了。

担心性能问题?

可能会担心,如此频繁的声明 lambda,会不会使得类的数量膨胀?官方提供的高阶函数,其实都是用inline关键字修饰的。意味着不仅高阶函数的调用会被函数的实际代码代替,而且声明的 lambda 也会被解析成具体的代码,而不是方法调用。Kotlin 高阶函数用 inline 关键字修饰,所以 lambda 不会生成新的 jvm class。在声明自己的高阶函数时,也建议用inline关键字修饰,防止类数量暴增。


还有一点,像mapfilter这样返回列表的高阶函数,每一次操作都会产生一个列表,这会不会增加垃圾回收的压力?答案是会的。但如果数据量不是万级别的,操作频率不是毫秒级别的,对性能的影响其实是很小的,是在移动端的场景更是难以遇到。但了解高阶函数对性能开销是很有必要的,可以避免一些耗性能的操作。

发布于: 刚刚阅读数: 4
用户头像

子不语Any

关注

If not now,when? 2022-09-17 加入

安卓攻城狮

评论

发布
暂无评论
Kotlin用高阶函数处理集合数据_android_子不语Any_InfoQ写作社区