在 Code Review 时,我经常看到同事写出几十行的 for 循环,里面夹杂着各种 if-else 和临时变量。
“兄弟,这代码看得我脑壳疼,能不能优化一下?”
“我也想啊,但逻辑太复杂了……”
其实,很多复杂的逻辑,用 Java 8 的 Stream API 几行代码就能搞定。它不仅让代码更简洁,还能利用并行流提升性能。
今天,我不讲基础的 filter 和 map,直接上 10 个生产环境中最常用的 Stream 高级技巧,让你的代码从“搬砖工”升级为“工匠”。
1. 🛠️ List 转 Map (分组与去重)
这是最常用的场景。比如把 List 转成 Map<Long, User>,方便根据 ID 快速查找。
青铜写法:
Map<Long, User> map = new HashMap<>();
for (User user : users) {
map.put(user.getId(), user);
}
王者写法:
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, user -> user));
⚠️ 避坑指南:
如果 users 中有重复的 ID,上面的代码会报错 Duplicate key。
稳健写法(保留后一个):
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(
User::getId,
Function.identity(),
(oldVal, newVal) -> newVal // 键冲突时,取新值
));
2. 📊 多级分组 (GroupingBy)
需求:先按“部门”分组,再按“性别”分组。
王者写法:
Map<String, Map<String, List<User>>> group = users.stream()
.collect(Collectors.groupingBy(
User::getDepartment,
Collectors.groupingBy(User::getGender)
));
一行代码搞定嵌套循环,清晰度爆表!
3. 🧮 分组统计 (Counting / Summing)
需求:统计每个部门的人数,或者每个部门的工资总和。
王者写法:
// 部门人数
Map<String, Long> countMap = users.stream()
.collect(Collectors.groupingBy(User::getDepartment, Collectors.counting()));
// 部门工资总和
Map<String, Double> salaryMap = users.stream()
.collect(Collectors.groupingBy(User::getDepartment, Collectors.summingDouble(User::getSalary)));
4. 🧹 集合去重 (Distinct by Property)
distinct() 默认是基于 equals() 方法去重的。如果只想根据对象的某个属性(如 name)去重怎么办?
自定义 Predicate:
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
// 使用
List<User> uniqueUsers = users.stream()
.filter(distinctByKey(User::getName))
.collect(Collectors.toList());
5. 🔗 字符串拼接 (Joining)
需求:把所有用户的名字提取出来,用逗号分隔。
青铜写法:StringBuilder 拼半天,最后还要删掉最后一个逗号。
王者写法:
String names = users.stream()
.map(User::getName)
.collect(Collectors.joining(", ", "[", "]"));
// 结果:[Alice, Bob, Charlie]
6. ⚡ 并行流 (Parallel Stream)
如果处理的数据量很大,且任务之间没有依赖,可以使用并行流利用多核 CPU。
// 效率提升神器,但要注意线程安全!
users.parallelStream().forEach(user -> {
// 处理复杂逻辑
heavyOperation(user);
});
注意:并行流使用的是公共的 ForkJoinPool,不要在里面执行阻塞式 I/O 操作(如数据库查询),否则会拖慢整个系统。
7. 🔍 查找与匹配 (AnyMatch / FindFirst)
需求:判断列表中是否有年龄大于 30 的用户。
王者写法:
boolean hasOldMan = users.stream().anyMatch(u -> u.getAge() > 30);
// 找到第一个符合条件的用户,找不到返回默认值
User user = users.stream()
.filter(u -> u.getAge() > 30)
.findFirst()
.orElse(new User("Default"));
8. 🔄 扁平化 (FlatMap)
需求:List 中每个 Order 有 List,现在要获取所有 Order 中的所有 Item。
王者写法:
List<Item> allItems = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.toList());
把“流中的流”铺平,非常实用!
9. 🧮 归约 (Reduce)
需求:计算所有商品的总价格(假设没有 summingDouble)。
王者写法:
BigDecimal total = products.stream()
.map(Product::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
10. 🛠️ 调试流 (Peek)
链式调用虽然爽,但中间步骤的数据长啥样?用 peek 偷看一眼。
List<User> result = users.stream()
.filter(u -> u.getAge() > 20)
.peek(u -> System.out.println("过滤后: " + u.getName())) // 打印日志,不影响流
.map(User::convertToDto)
.collect(Collectors.toList());
💡 架构师建议
- 可读性第一:虽然 Stream 可以写得很复杂,但如果逻辑太绕,建议还是拆开写,或者加注释。代码是写给人看的。
- 性能陷阱:对于简单的 for 循环(如数组遍历),Stream 的性能通常不如传统循环(因为有对象创建开销)。但在复杂的数据处理链中,Stream 的优势在于延迟执行和并行处理。
- 空指针保护:在使用 Stream 之前,务必判空!Optional.ofNullable(list).orElse(Collections.emptyList()).stream()...
掌握这些技巧,你的代码质量绝对能提升一个档次!赶紧去重构你的旧代码吧!