Skip to content

讲一下你平常用过哪些 stream 流的方式

约 930 字大约 3 分钟

Java基础小红书

2025-03-14

⭐ 题目日期:

小红书 - 2024/11/11

📝 题解:

在 Java 8 及更高版本中,Stream API 提供了一种高效且声明式的方式处理集合数据。以下是我在实际开发中常用的 Stream 操作方式及其典型应用场景,结合代码示例说明:


一、常用 Stream 操作及场景

1. 过滤数据(filter

  • 场景:从集合中筛选符合条件的数据。
  • 示例:过滤出年龄大于 18 的用户。
List<User> adults = users.stream()
    .filter(user -> user.getAge() > 18)
    .collect(Collectors.toList());

2. 数据转换(map

  • 场景:将集合元素转换为另一种类型或提取特定字段。
  • 示例:提取用户 ID 列表。
List<Long> userIds = users.stream()
    .map(User::getId)
    .collect(Collectors.toList());

3. 去重(distinct

  • 场景:去除集合中的重复元素。
  • 示例:获取不重复的城市列表。
List<String> cities = orders.stream()
    .map(Order::getCity)
    .distinct()
    .collect(Collectors.toList());

4. 排序(sorted

  • 场景:按指定规则对元素排序。
  • 示例:按用户年龄降序排序。
List<User> sortedUsers = users.stream()
    .sorted(Comparator.comparing(User::getAge).reversed())
    .collect(Collectors.toList());

5. 扁平化处理(flatMap

  • 场景:将嵌套集合展开为单一流。
  • 示例:获取所有订单的商品列表。
List<Product> products = orders.stream()
    .flatMap(order -> order.getProducts().stream())
    .collect(Collectors.toList());

6. 聚合计算(reduce

  • 场景:对集合元素进行累积计算(如求和、求极值)。
  • 示例:计算订单总金额。
double totalAmount = orders.stream()
    .mapToDouble(Order::getAmount)
    .sum();
// 或使用 reduce
double total = orders.stream()
    .map(Order::getAmount)
    .reduce(0.0, Double::sum);

7. 匹配检查(anyMatch/allMatch/noneMatch

  • 场景:快速验证集合是否满足条件。
  • 示例:检查是否存在未支付的订单。
boolean hasUnpaid = orders.stream()
    .anyMatch(order -> !order.isPaid());

8. 分组(groupingBy

  • 场景:按属性对元素分组。
  • 示例:按城市分组统计订单数量。
Map<String, Long> ordersByCity = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getCity,
        Collectors.counting()
    ));

9. 分区(partitioningBy

  • 场景:按布尔条件将元素分为两组。
  • 示例:将用户分为成年人和未成年人。
Map<Boolean, List<User>> partitionedUsers = users.stream()
    .collect(Collectors.partitioningBy(user -> user.getAge() >= 18));

10. 并行处理(parallelStream

  • 场景:大数据集并行处理以提高性能。
  • 示例:并行计算用户平均年龄。
double avgAge = users.parallelStream()
    .mapToInt(User::getAge)
    .average()
    .orElse(0);

二、实际应用案例

案例 1:数据清洗与转换

// 从 CSV 文件读取原始数据,过滤无效值并转换为 DTO
List<DataDTO> cleanData = rawDataList.stream()
    .filter(data -> data.isValid() && data.getTimestamp() != null)
    .map(data -> new DataDTO(data.getId(), data.getValue()))
    .collect(Collectors.toList());

案例 2:批量生成报告

// 统计每个部门的平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.averagingDouble(Employee::getSalary)
    ));

案例 3:接口数据聚合

// 合并多个服务返回的结果,去重并排序
List<Product> mergedProducts = Stream.of(serviceA.getProducts(), serviceB.getProducts())
    .flatMap(List::stream)
    .distinct()
    .sorted(Comparator.comparing(Product::getPrice))
    .collect(Collectors.toList());

三、使用 Stream 的最佳实践

  1. 优先声明式写法:用 filtermap 等操作代替循环和条件判断。
  2. 避免副作用:不要在 Stream 操作中修改外部变量(如使用 forEach 修改集合)。
  3. 性能敏感场景慎用并行
    1. 数据量小或操作简单时,串行流更高效。
    2. 确保线程安全(如避免共享可变状态)。
  4. 链式操作顺序优化
    1. 尽早使用 filter 减少后续操作的数据量。
    2. 将高计算成本的操作(如排序)放在链式调用后部。

四、总结

Stream API 通过链式调用和函数式编程,显著提升了代码的可读性和简洁性。掌握常用操作(如 filtermapcollect)并结合实际场景灵活使用,可以高效解决集合处理、数据转换和统计分析等问题。同时,注意并行流的适用场景和性能优化,能够进一步发挥 Stream 的优势。