Java 8 引入了一个功能强大的工具——Stream API,极大地简化了对集合的操作。传统上,Java 程序员习惯使用 for 循环来遍历集合并进行过滤、映射等操作,这种方式虽然直观但代码冗长且难以维护。Stream API 通过流式编程的方式,使得我们能够以更简洁和优雅的方式操作集合。

本文将介绍 Java Stream API 的基本概念及其常见的使用场景,帮助你更好地掌握这一工具。

一、什么是 Stream?

Stream 是一种用于处理集合的高级抽象。它并不是数据结构,而是一种可以在元素上执行聚合操作的流,比如 filter(过滤)、map(映射)、reduce(归约)等操作。Stream API 的核心思想是声明式编程,通过链式调用来描述数据处理过程。

需要注意的是,Stream 本身并不会存储数据,它是一个“流”,可以从集合、数组或 I/O 资源中获取数据,并通过一系列中间操作和终结操作来处理数据。

二、Stream 的基本操作

Stream API 的操作可以分为两类:

  1. 中间操作(Intermediate Operations):返回新的 Stream,可以被链式调用,但不会触发实际计算。例如:filtermapsorted 等。
  2. 终结操作(Terminal Operations):触发 Stream 计算并返回结果,比如 forEachcollectreduce 等。一旦执行终结操作,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 方法被调用时,filtermap 操作才会真正执行。每个元素会依次经过 filtermap 处理,输出如下:

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

在并行流中,mapreduce 操作会并行执行,可以显著缩短处理时间。然而,并行流并不总是能提高性能,在数据量较小或者有复杂依赖的情况下,可能会增加不必要的开销。

六、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 提供了一种声明式、简洁、高效的方式来处理集合数据。通过 filtermapsorted 等操作,开发者可以轻松地完成复杂的数据处理任务,而不用编写冗长的 for 循环。Stream API 的惰性求值和并行处理特性也为性能优化提供了支持。

掌握 Stream API 不仅能提升代码的可读性,也能显著提高 Java 应用的开发效率。


已注销
1 声望0 粉丝