☕ 你的 Java 代码太啰嗦了!掌握这 10 个 Stream 高级技巧,代码行数立减 50%

28 阅读3分钟

在 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());

💡 架构师建议

  1. 可读性第一:虽然 Stream 可以写得很复杂,但如果逻辑太绕,建议还是拆开写,或者加注释。代码是写给人看的。
  2. 性能陷阱:对于简单的 for 循环(如数组遍历),Stream 的性能通常不如传统循环(因为有对象创建开销)。但在复杂的数据处理链中,Stream 的优势在于延迟执行并行处理
  3. 空指针保护:在使用 Stream 之前,务必判空!Optional.ofNullable(list).orElse(Collections.emptyList()).stream()...

掌握这些技巧,你的代码质量绝对能提升一个档次!赶紧去重构你的旧代码吧!