Java 8 引入了一个功能强大的工具——Stream API,极大地简化了对集合的操作。传统上,Java 程序员习惯使用 for
循环来遍历集合并进行过滤、映射等操作,这种方式虽然直观但代码冗长且难以维护。Stream API 通过流式编程的方式,使得我们能够以更简洁和优雅的方式操作集合。
本文将介绍 Java Stream API 的基本概念及其常见的使用场景,帮助你更好地掌握这一工具。
一、什么是 Stream?
Stream 是一种用于处理集合的高级抽象。它并不是数据结构,而是一种可以在元素上执行聚合操作的流,比如 filter
(过滤)、map
(映射)、reduce
(归约)等操作。Stream API 的核心思想是声明式编程,通过链式调用来描述数据处理过程。
需要注意的是,Stream 本身并不会存储数据,它是一个“流”,可以从集合、数组或 I/O 资源中获取数据,并通过一系列中间操作和终结操作来处理数据。
二、Stream 的基本操作
Stream API 的操作可以分为两类:
- 中间操作(Intermediate Operations):返回新的 Stream,可以被链式调用,但不会触发实际计算。例如:
filter
、map
、sorted
等。 - 终结操作(Terminal Operations):触发 Stream 计算并返回结果,比如
forEach
、collect
、reduce
等。一旦执行终结操作,Stream 就会关闭。
示例集合
我们使用一个简单的整数列表来演示 Stream 的基本操作:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
三、常用的 Stream 操作
1. filter
:过滤
filter
方法用于对 Stream 中的元素进行筛选,保留符合条件的元素。它接受一个谓词(返回 boolean
的 lambda 表达式)作为参数。
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出:[2, 4, 6, 8, 10]
在上面的代码中,filter
方法过滤掉了所有的奇数,保留了偶数。
2. map
:映射
map
方法将每个元素映射为另一个值,常用于将集合中的元素转换成其他类型。
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares); // 输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
在这里,map
方法将每个元素都平方并返回一个新的 Stream。
3. sorted
:排序
sorted
方法用于对 Stream 中的元素进行排序。默认是升序排序,也可以传入自定义比较器来指定排序规则。
List<Integer> sortedNumbers = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出:[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
在这个例子中,我们使用 Comparator.reverseOrder()
实现了降序排序。
4. collect
:收集
collect
是一个终结操作,用于将 Stream 中的元素收集到某种结果中,比如列表、集合或字符串。Collectors
类提供了一系列工厂方法来方便地执行这种收集操作。
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出:[2, 4, 6, 8, 10]
上例中,collect
方法将流的元素收集到一个 List
中。
5. reduce
:归约
reduce
操作可以将 Stream 中的所有元素组合成一个结果,它经常用于求和、求积等聚合操作。
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum); // 输出:55
在这里,reduce
方法将所有元素相加并返回总和。
四、Stream 的惰性求值
Stream 的中间操作是惰性求值的。这意味着即使链式调用了多个中间操作,也不会立即执行,只有在遇到终结操作时才会执行计算。这种特性使得 Stream 可以进行延迟加载,避免不必要的计算,提高性能。
例如:
List<Integer> processedNumbers = numbers.stream()
.filter(n -> {
System.out.println("Filter: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("Map: " + n);
return n * n;
})
.collect(Collectors.toList());
System.out.println(processedNumbers);
在上面的代码中,只有在 collect
方法被调用时,filter
和 map
操作才会真正执行。每个元素会依次经过 filter
和 map
处理,输出如下:
Filter: 1
Filter: 2
Map: 2
Filter: 3
Filter: 4
Map: 4
...
这种惰性求值的特性使得 Stream 能够避免重复遍历,提高效率。
五、Stream 的并行处理
Java 8 引入了 parallelStream
方法,允许我们轻松地将 Stream 转换为并行流。在并行流中,多个线程会并行地处理 Stream 中的元素,利用多核 CPU 的优势来提高性能。
int sumOfSquares = numbers.parallelStream()
.map(n -> n * n)
.reduce(0, Integer::sum);
System.out.println(sumOfSquares); // 输出:385
在并行流中,map
和 reduce
操作会并行执行,可以显著缩短处理时间。然而,并行流并不总是能提高性能,在数据量较小或者有复杂依赖的情况下,可能会增加不必要的开销。
六、Stream API 的应用场景
Stream API 尤其适用于以下场景:
- 数据过滤:快速筛选集合中的元素。
- 数据转换:将一个类型的数据转换为另一种类型。
- 数据聚合:求和、求平均数、最值等操作。
- 并行处理:在大量数据处理时,提高处理速度。
例如,假设我们有一个 Person
对象的列表,想要找出所有年龄大于 18 岁的名字,并按年龄排序:
List<Person> people = Arrays.asList(
new Person("Alice", 23),
new Person("Bob", 17),
new Person("Charlie", 19)
);
List<String> adultNames = people.stream()
.filter(person -> person.getAge() > 18)
.sorted(Comparator.comparingInt(Person::getAge))
.map(Person::getName)
.collect(Collectors.toList());
System.out.println(adultNames); // 输出:[Charlie, Alice]
这种流式处理的方式使代码更加简洁明了。
七、总结
Java Stream API 提供了一种声明式、简洁、高效的方式来处理集合数据。通过 filter
、map
、sorted
等操作,开发者可以轻松地完成复杂的数据处理任务,而不用编写冗长的 for
循环。Stream API 的惰性求值和并行处理特性也为性能优化提供了支持。
掌握 Stream API 不仅能提升代码的可读性,也能显著提高 Java 应用的开发效率。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。