Java 8 Stream API:函数式数据流处理
Java 8 引入的 Stream API 是一个强大的工具集,它支持对集合数据进行函数式操作。该 API 允许开发者以更简洁、更具可读性且更高效的方式处理集合,显著提升了 Java 编程的生产力,是 Java 类库的一项重要增强。
Stream API 的核心理念是将数据处理步骤链接起来,形成一个函数式的数据流水线,从而执行过滤、映射、排序和归约等各种操作,避免了编写传统的显式循环代码。
以下是 Stream API 的关键概念和操作:
- Stream (流): Stream 是 Java 8 处理集合的核心抽象。它代表了一个数据管道,用于处理数据源产生的元素序列。数据源可以是集合、数组、I/O 操作等。Stream 的主要特点包括:
- Stream 不存储数据。
- Stream 不修改原始数据源,而是返回一个新的、包含结果的 Stream。
- Stream 操作是延迟执行的,仅在需要结果时才触发。
- 中间操作: 这些操作允许对 Stream 进行一系列数据处理。常见的中间操作包括
filter(过滤)、map(映射)、distinct(去重)、sorted(排序)、limit(限制数量) 和skip(跳过元素) 等。这些操作会返回一个新的 Stream。 - 终端操作: 终端操作是对 Stream 进行最终处理并产生结果的操作。一旦调用终端操作,Stream 就会被消耗,后续无法再进行中间操作。常见的终端操作有
forEach(遍历元素)、collect(将元素收集到集合)、reduce(执行归约操作,如求和、求最大值) 和count(计数) 等。 - 惰性求值: Stream 操作是惰性的,只有在调用终端操作时,中间操作才会被执行。这有助于提高性能,因为只有实际需要的数据才会被处理。
为什么选择 Stream API?
对于日常的应用程序开发,数据通常来源于数据库、文件等,需要 Java 程序进行处理。虽然传统的 for 循环可以处理集合数据,但 Stream API 提供了许多优势:
- 简洁性与可读性: Stream API 的链式调用使得代码更加紧凑和易于理解。
- 不可变性: Stream 操作不修改原始数据,而是创建新的 Stream,这保证了原始数据的不可变性,有利于并发编程。
- 惰性求值: Stream 操作的惰性特性意味着只有在终端操作被调用时,中间操作才会被执行。这提高了性能,因为只处理必要的数据。
- 并行处理能力: Stream API 支持并行处理数据,能够充分利用多核处理器的优势,从而提升性能。
- 开发效率: Stream API 减少了样板代码,提供了丰富的操作,使得代码编写更快。
Stream 关注的是计算过程,而集合关注的是数据本身。
Stream 操作的三个阶段
一个完整的 Stream 操作通常包含以下三个步骤:
1. 创建 Stream
首先需要获取一个 Stream 对象,常见的创建方式包括:
- 使用集合的
stream()方法: 集合接口提供了stream()(顺序流) 和parallelStream()(并行流) 方法来创建 Stream。// 示例:创建顺序流 Stream<String> sequentialStream = myList.stream(); // 示例:创建并行流 Stream<String> parallelStream = myList.parallelStream(); - 通过数组: 使用
Arrays.stream(T[] array)方法可以将数组转换为 Stream。String[] words = {"Hello", "World", "Java", "Stream"}; Stream<String> arrayStream = Arrays.stream(words); - 使用
Stream.of(T... values)方法: 此方法可以直接接收一组元素来创建 Stream。Stream<String> directStream = Stream.of("Apple", "Banana", "Cherry"); - 使用
Stream.builder()方法: Stream 提供了一个 builder 用于逐步构建 Stream。Stream.Builder<String> builder = Stream.builder(); builder.accept("First"); builder.accept("Second"); builder.accept("Third"); Stream<String> builtStream = builder.build(); - 使用
Stream.generate()或Stream.iterate(): 这两个方法可以生成无限元素的 Stream,通常需要结合limit()来限制元素数量。
这两个方法使用相对较少。// 生成包含 5 个 'X' 的 Stream Stream<String> generatedStream = Stream.generate(() -> "X").limit(5); // 生成从 0 开始,每次加 2 的整数序列,取前 5 个 Stream<Integer> iteratedStream = Stream.iterate(0, n -> n + 2).limit(5);
2. 中间操作
在获取 Stream 对象后,可以对其应用一系列中间操作。中间操作对 Stream 中的元素进行处理,例如过滤、映射、排序等。这些操作不会立即执行,而是会构建一个操作链。以下是常用的中间操作:
| 方法名 | 描述 |
|---|---|
filter(Predicate<T> predicate) |
根据指定的条件过滤元素。 |
map(Function<T, R> mapper) |
将流中的每个元素通过给定的函数转换为另一种类型的元素。 |
flatMap(Function<T, Stream<R>> mapper) |
将流中的每个元素映射成一个 Stream,然后将这些 Stream 合并成一个单一的 Stream。 |
distinct() |
移除流中的重复元素。 |
sorted() |
对流中的元素进行排序,默认使用自然顺序。 |
sorted(Comparator<T> comparator) |
使用指定的比较器对流中的元素进行排序。 |