Stream流式编程的最佳实践

Stream流式编程的最佳实践

Java 8 Stream API 提供了函数式风格的集合操作方式。本文从基础到高级,全面讲解 Stream 的正确用法和优化技巧。

Stream 核心概念

Stream 是对集合对象功能的增强,专注于对集合对象进行各种便利、高效的聚合操作。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * n) // 平方
.collect(Collectors.toList()); // 收集结果
// 结果: [4, 16, 36]

Stream 操作分类

中间操作(Intermediate)

返回新的 Stream,惰性执行:

操作 说明
filter 过滤元素
map 一对一映射
flatMap 扁平化映射
distinct 去重
sorted 排序
peek 查看元素(调试)
limit 截断
skip 跳过

终结操作(Terminal)

触发实际计算:

操作 说明
forEach 遍历消费
collect 收集到集合
reduce 归约计算
count 计数
anyMatch/allMatch/noneMatch 匹配判断
findFirst/findAny 查找元素
min/max 最值

创建 Stream 的多种方式

// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream(); // 串行
Stream<String> stream2 = list.parallelStream(); // 并行

// 从数组创建
Stream<String> stream3 = Arrays.stream(new String[]{"a", "b"});

// 从值创建
Stream<String> stream4 = Stream.of("a", "b", "c");

// 无限流
Stream<Integer> infinite = Stream.iterate(0, n -> n + 2);
Stream<Integer> limited = Stream.iterate(0, n -> n + 2).limit(100);

// 生成器
Stream<Double> random = Stream.generate(Math::random).limit(10);

// 空流
Stream<String> empty = Stream.empty();

// 拼接流
Stream<String> concat = Stream.concat(stream1, stream2);

常用操作详解

map vs flatMap

// map:一对一
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

// flatMap:一对多,扁平化
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);

List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 结果: [1, 2, 3, 4]

reduce 归约

// 求和
int sum = numbers.stream()
.reduce(0, Integer::sum);

// 拼接字符串
String joined = names.stream()
.reduce("", (a, b) -> a + "," + b);

// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);

collect 收集器

// 收集到List
List<String> list = stream.collect(Collectors.toList());

// 收集到Set
Set<String> set = stream.collect(Collectors.toSet());

// 收集到Map
Map<Long, String> map = users.stream()
.collect(Collectors.toMap(User::getId, User::getName));

// 分组
Map<String, List<User>> group = users.stream()
.collect(Collectors.groupingBy(User::getDepartment));

// 分区
Map<Boolean, List<Integer>> partition = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));

// 统计
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
// stats.getAverage(), stats.getMax(), stats.getCount()

// 连接字符串
String joined = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
// 结果: [Alice, Bob, Charlie]

并行流注意事项

List<Integer> numbers = new ArrayList<>();
// 填充大量数据

// 使用并行流
numbers.parallelStream()
.map(n -> heavyComputation(n))
.collect(Collectors.toList());

适合并行流的场景

  1. 数据量大(通常 > 10,000 条)
  2. 计算密集型操作
  3. 无状态、无副作用

不适合并行流的场景

  1. 数据量小(线程切换开销 > 并行收益)
  2. 需要严格顺序保证(forEachOrdered 性能差)
  3. 涉及共享可变状态
// 错误!并发修改共享变量
List<Integer> result = new ArrayList<>();
numbers.parallelStream().forEach(result::add); // 可能丢数据

// 正确:使用collect合并结果
List<Integer> result = numbers.parallelStream()
.collect(Collectors.toList());

性能优化技巧

1. 优先使用基本类型 Stream

// 有装箱开销
Stream<Integer> boxed = list.stream();
int sum1 = boxed.reduce(0, Integer::sum);

// 无装箱,性能更好
IntStream primitive = list.stream().mapToInt(Integer::intValue);
int sum2 = primitive.sum();

2. 短路操作提前返回

// 找到第一个匹配即停止
Optional<String> first = list.stream()
.filter(s -> s.startsWith("A"))
.findFirst(); // 短路操作

3. 避免重复遍历

// 错误:遍历两次
long count = list.stream().filter(...).count();
List<String> result = list.stream().filter(...).collect(...);

// 正确:收集后获取大小
List<String> result = list.stream().filter(...).collect(...);
long count = result.size();

常见陷阱

1. Stream 只能消费一次

Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.filter(s -> s.length() > 3); // IllegalStateException!

2. 不要在 Stream 中修改源数据

// 错误:ConcurrentModificationException
List<String> list = new ArrayList<>();
list.stream().forEach(list::remove);

// 正确:使用removeIf
list.removeIf(s -> s.length() < 3);

3. 注意空值处理

// 可能NPE
List<String> result = list.stream()
.map(s -> s.toUpperCase()) // s可能为null
.collect(Collectors.toList());

// 安全写法
List<String> result = list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.toList());

最佳实践总结

  1. 优先使用 Stream:替代传统 for 循环,代码更简洁
  2. 方法引用优于 Lambda:可读性更好
  3. 注意并行流的前提条件:数据量大 + 无共享状态
  4. 不要滥用 Stream:简单场景直接 for-each
  5. 避免在 Stream 中修改外部状态:保持函数式纯净
// 好的写法:声明式、简洁
Map<String, List<Employee>> byDept = employees.stream()
.filter(e -> e.getSalary() > 50000)
.sorted(Comparator.comparing(Employee::getName))
.collect(Collectors.groupingBy(Employee::getDepartment));

掌握 Stream API 是现代 Java 开发的必备技能,它能显著提升代码的简洁性和可读性。


   转载规则


《Stream流式编程的最佳实践》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录