Java 函数式编程常用套路(一)
本文我们着重来测试 Java 中常用基本流式编程算子。为了系统测试,我将整个测试单元划分成四个部分,分别是:
- 基础分组与分区
- 收集器下游变换
- 数值聚合与统计
- 多维高级矩阵
数据的准备
1 |
|
基础分组与分区
基本分组和分区主要包含的算子是 groupingBy(分组)与 partitioningBy(分区)。它们俩是 Collectors 体系里用来分流数据的两大核心,但设计理念截然不同:
- groupingBy:像多叉树,根据你指定的某个属性(Key),把数据分发到多个不同的盒子里,Key 的数量由数据决定。
- partitioningBy:像二叉树(分水岭),只接收一个返回 boolean 的断言,无论数据怎么变,结果永远只有两个盒子:true 盒 和 false 盒。
1 | /** |
收集器下游变换
上面的测试,我们分流后的盒子里装的都是整个 StaffPO 大对象。但在实际生产中,这种写法往往会带来很多不必要的内存开销,或者无法直接满足报表输出需求,所以我们还会用到下游收集器(Downstream Collector)。这里我们会重点测试 groupingBy 的双参数重载版本:
$$\text{groupingBy(classifier, downstream)}$$
这里的 downstream(下游收集器)就像是安置在每个分组盒子门口的 “二次加工流水线”,对刚分好组的数据进行再次处理。
1 | /** |
数值聚合与统计
在 Java 8 之前,如果你想计算每个部门的平均薪资、最高薪资和薪资总和,你需要写大量冗长的循环、累加器和条件判断。而在 Stream 体系中,通过将 groupingBy 与数值收集器结合,一行代码就能搞定复杂的财务报表。
1 | /** |
多维嵌套分组(矩阵)
在日常高难度业务中(比如复杂的动态权限矩阵、多维度的财务账单大盘),单级分组往往不够用。我们需要在分好组的数据盒子里,再次嵌套另一套分组或清洗规则。这里涉及到两个关键的操作:多级嵌套分组(套娃分组) 与 分组后提取极值对象(流流转换)。
1 | /** |
多表关联
将 List<Order> 和 List<User> 聚合成一个报表,要求输出:Map<String, Double> (部门名称 -> 该部门所有员工订单总金额)。
1 | public Map<String, Double> getDeptOrderAmount(List<Order> orders, List<User> users) { |
在上面的代码中,如果你有订单存在但用户已被删除(userMap.get() 返回 null),代码会抛出 NullPointerException。标准做法是在 groupingBy 的分组键位置,我们需要加入防御性编程:
1 | .collect(Collectors.groupingBy( |
遇到 downstream 这种写法,比如如果你的下游逻辑更复杂(比如:你既要平均值,又要最大值),此时一个 averagingDouble 就不够用了,你需要用到 Collectors.collectingAndThen 或 自定义对象封装。
1 | // 1. 定义一个简单的统计结果类 |