前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >14. Kotlin 使用高阶函数处理集合数据

14. Kotlin 使用高阶函数处理集合数据

作者头像
sickworm
发布2020-05-19 21:37:56
2.5K0
发布2020-05-19 21:37:56
举报
文章被收录于专栏:sickworm

本文将介绍如何使用 Kotlin 的高阶函数,如sumBy, reduce, fold, mapfilterforEach 等,来应对常见的集合数据处理场景。不了解高阶函数的同学可以先看下之前的文章。

遍历求和 sumBy

场景:输入一个账户列表List,求这些账户的财产总和sum

一般来说 Java 可以这样写:

代码语言:java
复制
public int getAccountsSum(List accounts) {
    int sum = 0;
    for (Account a: accounts) {
        sum += a.value;
    }
    return sum;
}

Kotlin 可以使用高阶函数 sumBy

代码语言:javascript
复制
val sum = accounts.sumBy { it.value }

那么sumBy做了什么呢?点击源码可以看到,其实它做的事情和上面 Java 实现的getAccountsSum是一样的,只是增加的值是通过我们传入的 lambda 来计算,而不是写死的Account.value这种通过传入函数来完成函数功能的函数,被称为高阶函数,高阶函数也因此具有很高的通用性和复用效率。

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

遍历求值 reduce

sumBy有一点不好,他只能求和,而且只接受IntDouble两种类型的值(sumBy:不然我起这个名字干嘛?)。如果我们要得到一个更复杂的逻辑的结果呢?

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

Java:

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

Kotlin 我们可以使用reduce

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

reduce 的逻辑是:将初始值acc设置为集合的第一个值,然后从第二个值开始,依次执行acc = lambda(acc, v),遍历完后返回acc。**reduce不仅限做加法运算,它比sumBy具有更广的通用性。

那如果reduce可以代替sumBy,为什么还需要sumBy?——因为它写起来更简单呀!

如果集合为空,reduce会抛出 UnsupportedOperationException(“Empty collection can’t be reduced.”)

更通用的遍历求值 fold

细心的同学已经发现了,sumBy的场景和reduce的场景用的是不同的数据结构。因为acc会被初始化为集合的第一个元素,所以reduce函数的输出也被限制为集合的范型类型。也就是说,sumBy的场景无法用reduce代替。

那 Kotlin 有没有能指定acc类型的高阶函数?有的,它叫fold

我们再回到sumBy的场景:输入一个账户列表List,求这些账户的财产总和sum

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

foldreduce多了一个参数——初始值,用来赋值给acc。得益于范型,我们可以通过这个办法来指定acc的类型。这样一来,fold可以完美替代sumBy的场景。而相比foldsumBy更专用,表意更清晰,写起来也更简洁。

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

JavaScript 的 reduce 函数就是 Kotlin 的 fold 函数。u1s1,Kotlin 的 reduce 函数挺危险的,还有类型限制,不建议使用。

过滤集合 filter

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

Java:

代码语言:java
复制
public List getPoorAccounts(List accounts) {
    List qbAccounts = new ArrayList<>(); // 想起虾米音乐的 穷逼 VIP 了
    for (Account a: accounts) {
        if (a.value < 100) {
            qbAccounts.add(a);
        }
    }
    return qbAccounts;
}

Kotlin 可以使用filter函数:

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

filter的逻辑是,新建一个空的 ArrayList(),然后把 lambda 返回值为 true 的元素加入到这个列表里。

列表生成列表 map

场景:输入一个账户列表List,找到所有资产大于 10000 的账户,封装成 VIP 账户返回:

Java:

代码语言:java
复制
public List getVipAccounts(List accounts) {
    List vipAccounts = new ArrayList<>();
    for (Account a: accounts) {
        if (a.value >= 10000) {
            vipAccounts.add(new VipAccount(a));
        }
    }
    return vipAccounts;
}

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

代码语言:javascript
复制
val vipAccounts = accounts
        .filter { it.value >= 10000 }
        .map { VipAccount(it) }

第一步我们用filter函数筛选出资产大于 10000 的账户,然后用map函数将过滤后的每一个账户转换为VipAccountmap的逻辑也很简单,它回返回一个和调用者大小相同的列表,具体的元素值为 lambda 的执行结果。

实在不适合,就用 forEach 吧

如果遇到了已知高阶函数都不适合的场景,不妨试试用forEach代替传统的 for 循环。为什么?因为写起来稍微简单一点。。

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

什么?你还想要 index下标?

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

不仅forEach有下标版本forEachIndexed,几乎所有高阶函数都有对应的 Indexed 版本。

Kotlin 官方提供了数十个高阶函数,但其实掌握了以上几个高阶函数,基本可以 cover 所有场景了。其他的只是写的简洁还是写的复杂一点的区别。而且你还有另一条路可以走:自己写一个特定的高阶函数。

担心性能?

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

大家可能担心的另一点,像mapfilter这样返回列表的高阶函数,每一次操作都会生成一个列表,这会不会增加垃圾回收的压力?答案是会的。但如果数据量不是万级别的,操作频率不是毫秒级别的,对性能的影响实在小之又小,特别是在移动端的场景更是难以遇到。但我们还是要了解高阶函数对性能开销,在对性能要求高的位置避免对象申请(如UI绘制的回调)。

Java 有高阶函数吗?

Java 也类似高阶函数的能力,如 Collections.sort 这种允许自定义排序的方法,和 Java 8 的 steam API。但因为 Java 没有 inline 无法有效的优化 lambda,且 Java 的 lambda 没有完整的闭包特性,无法修改外部变量。还有一些语法的原因,Java 的高阶函数使用起来相对没有那么舒服。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 遍历求和 sumBy
  • 遍历求值 reduce
  • 更通用的遍历求值 fold
  • 过滤集合 filter
  • 列表生成列表 map
  • 实在不适合,就用 forEach 吧
  • 担心性能?
  • Java 有高阶函数吗?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档