它是什么
Java 8 引入的"集合 + 函数式"组合——声明式处理数据:
List<Integer> result = nums.stream()
.filter(n -> n > 2)
.map(n -> n * 10)
.sorted()
.toList();
不是新集合类型——是操作管道。流式调用 + 末尾 collect / count / forEach 触发。
创建 Stream
List.of(1, 2, 3).stream();
Stream.of(1, 2, 3);
Arrays.stream(new int[]{1, 2, 3}); // IntStream
IntStream.range(0, 100); // 0..99
IntStream.rangeClosed(0, 100); // 0..100
Stream.iterate(1, n -> n * 2).limit(10); // 1, 2, 4, 8, ..., 512
Files.lines(path); // 文件每行(lazy)
主要中间操作
.filter(predicate) // 过滤
.map(function) // 转换
.mapToInt(...) / .mapToLong(...) / .mapToDouble(...) // 转基本类型流
.flatMap(function) // 扁平化(一对多)
.sorted() / .sorted(comparator) // 排序
.distinct() // 去重
.limit(n) // 取前 n
.skip(n) // 跳前 n
.peek(consumer) // 偷看(调试用)
终止操作
.collect(...) // 聚合到集合
.toList() // Java 16+,简化版
.toArray()
.count()
.findFirst() / .findAny() // 返 Optional
.anyMatch(p) / .allMatch(p) / .noneMatch(p)
.min(comparator) / .max(comparator)
.reduce(...) // 折叠
.forEach(consumer)
.forEachOrdered(consumer) // 并行流时保顺序
.sum() / .average() / .min() / .max() // IntStream/LongStream/DoubleStream
常见模式
过滤 + 转换 + 收集
List<String> adultNames = users.stream()
.filter(u -> u.getAge() >= 18)
.map(User::getName)
.sorted()
.toList();
分组(GroupBy)
Map<String, List<User>> byDept = users.stream()
.collect(Collectors.groupingBy(User::getDepartment));
// 分组 + 计数
Map<String, Long> countByDept = users.stream()
.collect(Collectors.groupingBy(User::getDepartment, Collectors.counting()));
// 分组 + 求和
Map<String, Integer> totalSalary = users.stream()
.collect(Collectors.groupingBy(
User::getDepartment,
Collectors.summingInt(User::getSalary)
));
转 Map
Map<Integer, String> idToName = users.stream()
.collect(Collectors.toMap(User::getId, User::getName));
// 处理重复 key
Map<Integer, String> dedup = users.stream()
.collect(Collectors.toMap(
User::getId, User::getName,
(existing, replacement) -> existing // 冲突时保留原值
));
Join 字符串
String csv = users.stream()
.map(User::getName)
.collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"
String wrapped = users.stream()
.map(User::getName)
.collect(Collectors.joining(", ", "[", "]"));
// "[Alice, Bob, Charlie]"
数值聚合(IntStream)
int sum = users.stream().mapToInt(User::getAge).sum();
double avg = users.stream().mapToInt(User::getAge).average().orElse(0);
IntSummaryStatistics stats = users.stream().mapToInt(User::getAge).summaryStatistics();
// stats.getSum() / getAverage() / getMin() / getMax() / getCount()
flatMap:扁平化
List<List<Integer>> nested = List.of(
List.of(1, 2),
List.of(3, 4, 5),
List.of(6)
);
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.toList();
// [1, 2, 3, 4, 5, 6]
reduce:折叠
int sum = nums.stream().reduce(0, Integer::sum); // 带初始值
Optional<Integer> max = nums.stream().reduce(Integer::max); // 无初始值(空时返 Optional.empty)
String concat = strs.stream().reduce("", String::concat);
通常 sum / min / max / joining 已经能解决——reduce 在自定义聚合时用。
并行流(parallelStream)
int sum = nums.parallelStream().mapToInt(Integer::intValue).sum();
慎用:
- 共享 ForkJoinPool(默认 Common Pool,所有 parallelStream 抢)
- 小数据更慢(线程调度开销)
- 操作要无副作用、可结合(commutative)
- 在 Web 容器 / 线程池里用容易出意外
经验:不剖析不并行——只有证明慢、且数据量大(10k+)、操作 CPU 重时考虑。日常用串行流就够。
流是一次性的
var s = list.stream().filter(...);
s.toList(); // ✅
s.toList(); // ❌ IllegalStateException: stream has already been operated upon
每次需要新流——再调一次 .stream()。
不要在流里改外部状态
List<Integer> result = new ArrayList<>();
nums.stream()
.filter(n -> n > 0)
.forEach(result::add); // ❌ 副作用,并行流时会出错
List<Integer> result = nums.stream() // ✅ 用 collect
.filter(n -> n > 0)
.toList();
Java 16+ 的简化
.toList() // 替代 .collect(Collectors.toList()),返不可变 List
.mapMulti(...) // flatMap 的更高效版本(Java 16+)
// Stream.toList() 返回的是不可变 List
list.stream().toList().add(1); // ❌ UnsupportedOperationException
性能 vs 普通 for
// for 循环
int sum = 0;
for (int n : nums) if (n > 0) sum += n;
// stream
int sum2 = nums.stream().filter(n -> n > 0).mapToInt(Integer::intValue).sum();
stream 通常比 for 慢 2-5 倍——但对 99% 业务代码无意义(差几微秒)。优先用 stream 提高可读性。
热点循环 / 1M+ 元素 → 写 for。
Files.lines + Stream
try (var lines = Files.lines(Path.of("data.txt"))) {
long count = lines.filter(l -> !l.isBlank()).count();
}
注意 try-with-resources——流持有文件句柄,必须关闭。
→ 下一篇 Optional