Java 8 引入了 Stream API ,这是 Java 集合框架的一个重要扩展。Stream 流提供了一种高效、函数式的方式来处理集合数据(如 List
、Set
、Map
等),它允许开发者以声明式的方式对数据进行操作,而不是通过传统的迭代方式。
以下是对 Java 8 中 Stream 流的详细理解:
1. Stream 的核心概念
(1) 什么是 Stream?
- Stream 是一个来自数据源(如集合、数组、文件等)的元素序列,支持顺序或并行的聚合操作。
- 它不是一种数据结构,而是一种用于处理数据的操作管道。
- Stream 不会存储数据,而是对数据进行操作,并返回结果。
(2) Stream 的特点
-
惰性求值(Lazy Evaluation) :
- Stream 的操作分为 中间操作(Intermediate Operations) 和 终端操作(Terminal Operations) 。
- 中间操作是惰性的,只有在终端操作触发时才会执行。
-
不可变性 :
- Stream 的操作不会修改原始数据源,而是生成一个新的 Stream 或结果。
-
一次使用 :
- Stream 只能被消费一次,再次使用需要重新创建。
-
支持并行处理 :
- Stream 提供了并行流(
parallelStream
),可以利用多核 CPU 并行处理数据。
- Stream 提供了并行流(
2. Stream 的操作流程
Stream 的操作通常分为以下几个步骤:
- 创建 Stream :从数据源(如集合、数组)中获取 Stream。
- 中间操作 :对 Stream 进行一系列转换操作(如过滤、映射、排序等)。
- 终端操作 :触发 Stream 的执行,并生成结果(如收集到集合、计算总和等)。
3. Stream 的创建
(1) 从集合创建 Stream
List
>Stream<String> stream = list.stream(); // 创建顺序流
Stream<String> parallelStream = list.parallelStream(); // 创建并行流
(2) 从数组创建 Stream
ini
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
(3) 从值创建 Stream
java
arduino
Stream<String> stream = Stream.of("a", "b", "c");
(4) 从文件创建 Stream
java
js
Stream<String> lines = Files.lines(Paths.get("file.txt"));
(5) 从生成器创建 Stream
java
arduino
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5); // 生成 0, 2, 4, 6, 8
Stream<Double> randomStream = Stream.generate(Math::random).limit(5); // 生成 5 个随机数
4. 中间操作(Intermediate Operations)
中间操作返回一个新的 Stream,它们是惰性的,只有在终端操作触发时才会执行。
(1) filter
过滤符合条件的元素。
ini
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println); // 输出 2, 4
(2) map
将每个元素映射为另一种形式。
java
rust
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // 输出 ALICE, BOB, CHARLIE
(3) flatMap
将每个元素映射为多个元素,并将结果扁平化为单一流。
java
scss
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
nestedList.stream()
.flatMap(List::stream)
.forEach(System.out::println); // 输出 1, 2, 3, 4
(4) distinct
去除重复元素。
java
scss
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4);
numbers.stream()
.distinct()
.forEach(System.out::println); // 输出 1, 2, 3, 4
(5) sorted
对元素进行排序。
java
scss
List<Integer> numbers = Arrays.asList(4, 2, 5, 1, 3);
numbers.stream()
.sorted()
.forEach(System.out::println); // 输出 1, 2, 3, 4, 5
(6) peek
对流中的每个元素执行操作,主要用于调试。
java
scss
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.peek(n -> System.out.println("Before: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("After: " + n))
.collect(Collectors.toList());
5. 终端操作(Terminal Operations)
终端操作触发 Stream 的执行,并生成结果。
(1) forEach
遍历流中的每个元素。
java
ini
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);
(2) collect
将流的结果收集到集合或其他数据结构中。
java
ini
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
(3) reduce
对流中的元素进行归约操作。
java
ini
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // 结果为 15
(4) count
统计流中元素的数量。
java
long count = numbers.stream().count();
(5) anyMatch / allMatch / noneMatch
检查流中是否满足某种条件。
java
ini
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // 是否存在偶数
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // 是否全部为偶数
boolean noneEven = numbers.stream().noneMatch(n -> n % 2 == 0); // 是否不存在偶数
(6) findFirst / findAny
查找第一个或任意一个元素。
java
ini
Optional<Integer> first = numbers.stream().findFirst();
Optional<Integer> any = numbers.stream().findAny();
6. 并行流(Parallel Streams)
并行流利用多核 CPU 并行处理数据,适合处理大量数据。
java
ini
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
需要注意的是,并行流并不总是更快,因为它引入了线程管理的开销。对于小规模数据或复杂操作,顺序流可能更高效。
7. Stream 的优点
- 简洁性 :Stream 提供了声明式的操作方式,代码更加简洁易读。
- 可组合性 :中间操作可以链式调用,形成复杂的操作管道。
- 并行支持 :轻松实现并行处理,提升性能。
- 延迟执行 :中间操作只在终端操作触发时执行,避免不必要的计算。
8. Stream 的注意事项
- 一次性使用 :Stream 只能被消费一次,再次使用需要重新创建。
- 无状态操作优先 :尽量避免有状态操作(如
sorted
),因为它们可能会影响性能。 - 避免副作用 :Stream 操作应该是纯函数式的,避免修改外部变量。
- 并行流的适用性 :并行流适合处理大规模数据,但对于小规模数据或复杂操作,顺序流可能更高效。