如果像 AI 一样写 Lambda(第 4 篇):聚合与收集(Stream 应用篇)
一句话总结:流处理完总要"收摊"。
Collectors.toList/joining/groupingBy/reduce是把流变回集合、字符串、Map、单值的四大收摊工具,它们本身就是::的重度用户。
相关文档:《如果像AI一样写Lambda-第3篇-转换与过滤-通俗版.md》——先加工,再收摊。
0. 比喻:收银台汇总
| 写法 | 比喻 |
|---|---|
| 普通人的写法 | 一个个手记,最后自己算总账 |
| 函数式写法 | 把"汇总规则"告诉收银机 |
| AI lambda 简写 | 规则直接指到现成方法,比如"求和"用Integer::sum表达 |
1. 流变 List:collect(toList)
场景:加工完的流,装回一个 List。
① 普通人的写法:for 循环逐个装
List<String>result=newArrayList<>();for(Stringname:names){if(name!=null){result.add(name.trim());}}② 函数式写法:filter + map + collect
List<String>result=names.stream().filter(name->name!=null).map(name->name.trim()).collect(Collectors.toList());③ AI lambda 简写:全链路方法引用
List<String>result=names.stream().filter(Objects::nonNull).map(String::trim).collect(Collectors.toList());Collectors.toList()是固定搭配,AI 几乎不会去 new ArrayList 手抄。filter → map → collect三段式的组合正是第 3 篇两个 API 的串联。
2. 流变字符串:joining
场景:把列表拼成逗号分隔的字符串。
① 普通人的写法:StringBuilder 手拼
List<String>items=Arrays.asList("A","B","C");StringBuildersb=newStringBuilder();for(inti=0;i<items.size();i++){if(i>0){sb.append(",");}sb.append(items.get(i));}Stringresult=sb.toString();// "A,B,C"② 函数式写法:collect(joining)
Stringresult=items.stream().collect(Collectors.joining(","));③ AI lambda 简写:先流式加工再拼接
Stringresult=names.stream().filter(Objects::nonNull).map(String::trim).collect(Collectors.joining(","));补充:如果只是纯列表拼接(不加工),String.join(",", items)比 stream 更短;joining的舞台在"先过滤、再转换、最后拼"的组合拳里。
3. 流变 Map:groupingBy
场景:按某个规则把列表分组,变成 Map。
① 普通人的写法:for 循环 + computeIfAbsent
Map<String,List<String>>groupMap=newHashMap<>();for(Stringname:names){Stringkey=name.substring(0,2);// 按前两个字符分组groupMap.computeIfAbsent(key,k->newArrayList<>()).add(name);}② 函数式写法:groupingBy + lambda
Map<String,List<String>>groupMap=names.stream().collect(Collectors.groupingBy(name->name.substring(0,2)));③ AI lambda 简写:分组 + 计数一步到位
Map<String,Long>counts=names.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));groupingBy的第二个参数可以继续嵌套收集器,统计、再映射都能一行完成,这是它被 AI 偏爱的原因。
4. 流变单值:reduce
场景:把一堆数合成一个结果(求和、求最大值)。
① 普通人的写法:for 循环累加
List<Integer>nums=Arrays.asList(1,2,3,4,5);intsum=0;for(intn:nums){sum+=n;}② 函数式写法:reduce + 完整 lambda
intsum=nums.stream().reduce(0,(a,b)->a+b);③ AI lambda 简写:多参数方法引用
intsum=nums.stream().reduce(0,Integer::sum);Integer::sum等价于(a, b) -> Integer.sum(a, b)——方法引用同样支持多参数,只要参数个数对得上。求最大值同理:reduce(Integer::max)。
5. 四大收摊工具对照表
| 工具 | 目标形态 | 典型用途 | 一句话记忆 |
|---|---|---|---|
Collectors.toList() | List | 过滤/转换后落盘 | 流 → 列表 |
Collectors.joining(",") | String | 拼接展示 | 流 → 字符串 |
Collectors.groupingBy(...) | Map | 分组统计 | 流 → 分组 |
reduce(0, Integer::sum) | 单值 | 求和/求极值 | 流 → 单值 |
6. 陷阱:Collectors.toMap 重复 key 会炸
groupingBy自动处理重复 key(合并成 List),但toMap不会:
// ❌ 两个元素算出的 key 相同 → 抛 IllegalStateExceptionMap<String,String>map=names.stream().collect(Collectors.toMap(name->name.substring(0,2),Function.identity()));// ✅ 必须给出冲突策略(k1, k2) -> k2 表示后到的覆盖先到的Map<String,String>map=names.stream().collect(Collectors.toMap(name->name.substring(0,2),Function.identity(),(k1,k2)->k2));不确定 key 是否唯一时,groupingBy永远比toMap安全。
一页速记
- 收摊四件套:流变 List 用
toList,变字符串用joining,变 Map 用groupingBy,变单值用reduce filter → map → collect是 AI 处理列表的标准三段式- 纯列表拼接用
String.join(",", items),流式加工后才用joining reduce(0, Integer::sum)= 求和,reduce(Integer::max)= 求最大值- 方法引用支持多参数:
(a, b) -> a + b=Integer::sum toMap遇重复 key 会抛异常,必须写第三参(k1, k2) -> k2;不确定唯一就用groupingBy