Java8(三)--Stream
目录
一、核心概念:
1、什么是 Stream?
非存储结构:不直接修改底层数据源,仅描述计算过程。
流水线操作:由零或多个中间操作(Intermediate)和一个终止操作(Terminal)组成。
惰性求值:中间操作延迟执行,直到触发终止操作。
2、与集合的区别:
| 特性 | 集合(Collection) | Stream |
|---|---|---|
| 数据存储 | 存储实际数据 | 不存储数据 |
| 遍历方式 | 外部迭代(显式循环) | 内部迭代(自动处理) |
| 可复用性 | 可多次遍历 | 一次性消费 |
| 数据处理 | 命令式(How) | 声明式(What) |
二、创建 Stream:
1、基础创建方式:
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// 直接创建元素流
Stream<String> stream3 = Stream.of("a", "b", "c");
// 生成无限流
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
2、特殊类型流:
// 基本类型流(避免装箱开销)
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1,5); // 1,2,3,4,5
// 文件流
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
lines.forEach(System.out::println);
}
三、核心操作:
1、中间操作(Intermediate Operations):
| 操作 | 描述 | 示例 |
|---|---|---|
filter |
条件过滤 | .filter(s -> s.length() > 3) |
map |
元素转换 | .map(String::toUpperCase) |
flatMap |
扁平化嵌套结构 | .flatMap(list -> list.stream()) |
distinct |
去重 | .distinct() |
sorted |
排序 | .sorted(Comparator.reverseOrder()) |
peek |
调试观察元素 | .peek(System.out::println) |
limit/skip |
限制元素数量 | .limit(5).skip(2) |
2、终止操作(Terminal Operations):
| 操作 | 描述 | 示例 |
|---|---|---|
forEach |
遍历元素 | .forEach(System.out::println) |
collect |
转换为集合 | .collect(Collectors.toList()) |
reduce |
聚合计算 | .reduce(0, Integer::sum) |
count |
统计元素数量 | .count() |
anyMatch |
任意匹配 | .anyMatch(s -> s.contains("a")) |
findFirst |
获取第一个元素 | .findFirst() |
四、高阶应用:
1、自定义收集器:
实现 Collector 接口处理复杂聚合:
// 统计字符串长度总和
Collector<String, ?, Integer> lengthSumCollector =
Collector.of(
() -> new int[1], // Supplier 初始化容器
(container, str) -> container[0] += str.length(), // Accumulator
(c1, c2) -> { c1[0] += c2[0]; return c1; }, // Combiner(并行用)
container -> container[0] // Finisher
);
int totalLength = Stream.of("Java", "Stream")
.collect(lengthSumCollector); // 4 + 6 = 10
2、并行流优化:
通过 parallel() 启用并行处理:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 并行计算平方和
int sum = numbers.parallelStream()
.mapToInt(n -> n * n)
.sum(); // 1+4+9+16+25+36=91
并行流注意事项:
数据规模需足够大(推荐 N > 10,000)
避免共享可变状态
底层使用 ForkJoinPool(默认线程数=CPU核心数)
五、性能与最佳实践:
1、操作链优化:
合并过滤条件:减少中间操作次数
// 劣化写法
.filter(s -> s.length() > 3)
.filter(s -> s.startsWith("A"))
// 优化写法
.filter(s -> s.length() > 3 && s.startsWith("A"))
优先使用基本类型流(IntStream/LongStream)避免装箱开销
2、短路操作利用:
利用 anyMatch/findFirst 提前终止计算:
boolean hasAdmin = users.stream()
.anyMatch(u -> u.isAdmin()); // 找到第一个即停止
3、避免副作用:
保持函数纯度(无状态、不修改外部变量):
// 错误示例(修改外部变量)
List<String> result = new ArrayList<>();
stream.filter(s -> s.startsWith("A"))
.forEach(s -> result.add(s)); // 可能引发并发问题
// 正确写法
List<String> result = stream.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());
六、典型应用场景:
1、数据转换:
// 对象属性提取
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList());
2、分组统计:
// 按部门分组统计薪资
Map<String, Double> deptSalaries = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
将list转化为Map,这里需要注意key重复的场景(如果不处理可能会导致异常),是新值覆盖旧值,还是一致保留旧值,又或者对重复的场景做额外的操作都是支持的。
Map<Integer, String> userMap = users.stream()
.collect(Collectors.toMap(
User::getAge,
User::getName,
(first, second) -> first // 始终保留第一个值
));
// 结果: {25=Alice, 30=Charlie}
Map<Integer, String> mergedNamesMap = users.stream()
.collect(Collectors.toMap(
User::getAge,
User::getName,
(existing, newName) -> existing + ", " + newName // 拼接重复值
));
// 结果: {25=Alice, Bob, 30=Charlie}
3、复杂流水线:
// 找出价格>100的前3个商品名称(按价格降序)
List<String> topItems = products.stream()
.filter(p -> p.getPrice() > 100)
.sorted(Comparator.comparing(Product::getPrice).reversed())
.limit(3)
.map(Product::getName)
.collect(Collectors.toList());
七、常见误区:
1、重复使用流:
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);
stream.count(); // 抛出 IllegalStateException
2、混淆中间/终止操作:
Stream.of("a", "b", "c").sorted(); // 错误!缺少终止操作
3、过度使用并行流:
可能引发线程安全问题或性能下降
八、与传统循环对比:
案例:统计长单词数量
// 传统方式
int count = 0;
for (String word : words) {
if (word.length() > 5) {
count++;
}
}
// Stream 方式
long count = words.stream()
.filter(word -> word.length() > 5)
.count();
优势分析:
代码更简洁(减少模板代码)
易于实现并行处理(.parallelStream())
支持更复杂的链式操作
九、总结:
Java Stream 通过声明式编程范式,显著提升了集合数据处理的表达力和可维护性。掌握以下要点可最大化其价值:
理解惰性求值机制,合理设计操作链
优先选择无状态函数,避免副作用
根据场景选择并行流,注意线程安全
善用 Collectors 工具类处理复杂聚合
随着函数式编程的普及,Stream 已成为现代 Java 开发的必备技能。合理运用可使代码既简洁高效,又具备良好的可扩展性。

浙公网安备 33010602011771号