Stream流式编程

Stream简介

什么是 流式编程

通过将集合转换为这么一种叫做 “流” 的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。

Stream(流)是 Java 8 引入的一个新的抽象概念,它代表着一种处理数据的序列。

简单来说,Stream 是一系列元素的集合,这些元素可以是集合、数组、I/O 资源或者其他数据源。

Stream API 提供了丰富的操作方法,可以对 Stream 中的元素进行各种转换、过滤、映射、聚合等操作,从而实现对数据的处理和操作。

Stream API 的设计目标是提供一种高效、可扩展和易于使用的方式来处理大量的数据。

Java Stream 的主要作用:

  • 简化集合操作:使用传统的 for 循环或迭代器来处理集合数据可能会导致冗长而复杂的代码。而使用流式编程,能够用更直观、更简洁的方式对集合进行过滤、映射、排序、聚合等操作,使代码变得更加清晰易懂。
  • 延迟计算:流式操作允许你在处理数据之前定义一系列的操作步骤,但只在需要结果时才会实际执行。这种延迟计算的特性意味着可以根据需要动态调整数据处理的操作流程,提升效率。
  • 并行处理:Java Stream 提供了并行流的支持,可以将数据分成多个块进行并行处理,从而充分利用多核处理器的性能优势,提高代码的执行速度。
  • 函数式编程风格:流式编程鼓励使用函数式编程的思想,通过传递函数作为参数或使用 Lambda 表达式来实现代码的简化和灵活性。这种函数式的编程模式有助于减少副作用,并使代码更易测试和调试。

Stream 的特性和优势

  • 简化的编程模型:Stream 提供了一种更简洁、更声明式的编程模型,使代码更易于理解和维护。通过使用 Stream API,我们可以用更少的代码实现复杂的数据操作,将关注点从如何实现转移到了更关注我们想要做什么。
  • 函数式编程风格:Stream 是基于函数式编程思想设计的,它鼓励使用不可变的数据和纯函数的方式进行操作。这种风格避免了副作用,使代码更加模块化、可测试和可维护。此外,Stream 还支持 Lambda 表达式,使得代码更加简洁和灵活。
  • 惰性求值:Stream 的操作是惰性求值的,也就是说在定义操作流程时并不会立即执行计算。只有当终止操作被调用时,才会触发实际的计算过程。这种特性可以避免对整个数据集进行不必要的计算,提高了效率。
  • 并行处理能力:Stream 支持并行处理,在某些情况下可以通过 parallel() 方法将流转换为并行流,利用多核处理器的优势来提高处理速度。并行流能够自动将数据划分为多个子任务,并在多个线程上同时执行,提高了处理大量数据的效率。
  • 优化的性能:Stream API 内部使用了优化技术,如延迟执行、短路操作等,以提高计算性能。Stream 操作是通过内部迭代器实现的,可以更好地利用硬件资源,并适应数据规模的变化。
  • 支持丰富的操作方法:Stream API 提供了许多丰富的操作方法,如过滤、映射、排序、聚合等。这些方法可以按需求组合起来形成一个操作流程。在组合多个操作时,Stream 提供了链式调用的方式,使代码更加简洁和可读性更强。
  • 可以操作各种数据源:Stream 不仅可以操作集合类数据,还可以操作其他数据源,如数组、I/O 资源甚至无限序列。这使得我们可以使用相同的编程模型来处理各种类型的数据。

为什么使用流式编程可以提高代码可读性和简洁性

  • 声明式编程风格:流式编程采用了一种声明式的编程风格,你只需描述你想要对数据执行的操作,而不需要显式地编写迭代和控制流语句。这使得代码更加直观和易于理解,因为你可以更专注地表达你的意图,而无需关注如何实现。
  • 链式调用:流式编程使用方法链式调用的方式,将多个操作链接在一起。每个方法都返回一个新的流对象,这样你可以像“流水线”一样在代码中顺序地写下各种操作,使代码逻辑清晰明了。这种链式调用的方式使得代码看起来更加流畅,减少了中间变量和临时集合的使用。
  • 操作的组合:流式编程提供了一系列的操作方法,如过滤、映射、排序、聚合等,这些方法可以按照需要进行组合使用。你可以根据具体的业务需求将这些操作串联起来,形成一个复杂的处理流程,而不需要编写大量的循环和条件语句。这种组合操作的方式使得代码更加模块化和可维护。
  • 减少中间状态:传统的迭代方式通常需要引入中间变量来保存中间结果,这样会增加代码的复杂度和维护成本。而流式编程将多个操作链接在一起,通过流对象本身来传递数据,避免了中间状态的引入。这种方式使得代码更加简洁,减少了临时变量的使用。
  • 减少循环和条件:流式编程可以替代传统的循环和条件语句的使用。例如,可以使用 filter() 方法进行元素的筛选,使用 map() 方法进行元素的转换,使用 reduce() 方法进行聚合操作等。这些方法可以用一行代码完成相应的操作,避免了繁琐的循环和条件逻辑,使得代码更加简洁明了。

流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”

Stream流处理的特性

① Stream 自己不存储数据

② Stream 不会改变源对象

③ Stream 操作是延迟执行的。

Stream操作的三个步骤

  • 创建 Stream

一个数据源(如:集合、数组), 获取一个流。

  • 中间操作

一个中间操作链,对数据源的数据进行处理。

  • 终止操作(终端操作)

只有当执行终止操作时,才会执行中间操作链,并产生结果 。并且执行完终止操作后不可以再执行中间操作

流程图:

创建 Stream 对象

从集合创建:我们可以通过调用集合的 stream() 方法来创建一个 Stream 对象。例如:

1
2
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();

从数组创建:Java 8 引入了 Arrays 类的 stream() 方法,我们可以使用它来创建一个 Stream 对象。例如:

1
2
String[] names = {"Alice", "Bob", "Carol"};
Stream<String> stream = Arrays.stream(names);

通过 Stream.of() 创建:我们可以使用 Stream.of() 方法直接将一组元素转换为 Stream 对象。例如:

1
2
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

通过 Stream.builder() 创建:如果我们不确定要添加多少个元素到 Stream 中,可以使用 Stream.builder() 创建一个 Stream.Builder 对象,并使用其 add() 方法来逐个添加元素,最后调用 build() 方法生成 Stream 对象。例如:

1
2
3
4
5
解释Stream.Builder<String> builder = Stream.builder();
builder.add("Apple");
builder.add("Banana");
builder.add("Cherry");
Stream<String> stream = builder.build();

从 I/O 资源创建:Java 8 引入了一些新的 I/O 类(如 BufferedReader、Files 等),它们提供了很多方法来读取文件、网络流等数据。这些方法通常返回一个 Stream 对象,可以直接使用。例如:

1
2
3
4
5
6
解释Path path = Paths.get("data.txt");
try (Stream<String> stream = Files.lines(path)) {
// 使用 stream 处理数据
} catch (IOException e) {
e.printStackTrace();
}

通过生成器创建:除了从现有的数据源创建 Stream,我们还可以使用生成器来生成元素。Java 8 中提供了 Stream.generate() 方法和 Stream.iterate() 方法来创建无限 Stream。例如:

1
2
Stream<Integer> stream = Stream.generate(() -> 0); // 创建一个无限流,每个元素都是 0
Stream<Integer> stream = Stream.iterate(0, n -> n + 1); // 创建一个无限流,从 0 开始递增

需要注意的是,Stream 对象是一种一次性使用的对象,它只能被消费一次。一旦对 Stream 执行了终止操作(如收集结果、遍历元素),Stream 就会被关闭,后续无法再使用。因此,在使用 Stream 时,需要根据需要重新创建新的 Stream 对象。

Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

1.筛选与切片

微信图片_20211118175113.jpg

2.映射

微信图片_20211118175116.jpg

3.排序

微信图片_20211118175118.jpg

Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、 Integer,甚至是 void 。

1.查找与匹配

微信图片_20211118175157.jpg

微信图片_20211118175158.jpg

2.规约

微信图片_20211118175215.jpg

3.收集

微信图片_20211118175217.jpg

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表

微信图片_20211118175227.jpg

微信图片_20211118175251.jpg