外观
讲一下你平常用过哪些 stream 流的方式
⭐ 题目日期:
小红书 - 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 的最佳实践
- 优先声明式写法:用
filter
、map
等操作代替循环和条件判断。 - 避免副作用:不要在 Stream 操作中修改外部变量(如使用
forEach
修改集合)。 - 性能敏感场景慎用并行流:
- 数据量小或操作简单时,串行流更高效。
- 确保线程安全(如避免共享可变状态)。
- 链式操作顺序优化:
- 尽早使用
filter
减少后续操作的数据量。 - 将高计算成本的操作(如排序)放在链式调用后部。
- 尽早使用
四、总结
Stream API 通过链式调用和函数式编程,显著提升了代码的可读性和简洁性。掌握常用操作(如 filter
、map
、collect
)并结合实际场景灵活使用,可以高效解决集合处理、数据转换和统计分析等问题。同时,注意并行流的适用场景和性能优化,能够进一步发挥 Stream 的优势。