Java 8 中的 Java Streams 简介

作为Java 8的一部分引入的 Java Streams用于处理数据集合。它本身不是数据结构,但可用于在排序和流水线的帮助下从其他数据结构获取输入以获得最终输出。

因为它不是一个单独的数据结构,所以它也从不真正改变数据源。因此,可以说 Java 8 中的 Java 流具有以下特性:

  1. Java 流可以在Java中的“java.util.stream”包的帮助下使用。这可以使用以下语句导入到脚本中:import java.util.stream.* ;使用它,我们还可以轻松地在 Java 流上实现多个内置函数。
  1. Java 流不是数据结构。它可以从数据集合中获取输入,例如 Java 中的集合和数组。
  2. Java 流不涉及输入数据结构的更改。
  3. Java 流不会改变源。相反,它通过相应的流水线方法生成输出。
  4. Java 流经历中间和终端操作,我们将在后面的部分中讨论。
  5. 在 Java 流中,中间操作是流水线和惰性计算的。它们由终端功能终止。这形成了使用 Java 流的基本格式。

在下一节中,我们将了解 Java 8 中用于根据需要创建 Java 流的各种方法。

在 Java 8 中创建 Java 流

可以使用多种方式创建 Java Streams。其中一些列在本节中,如下所示:

  1. 使用 Stream.empty() 方法创建空流

人们可能会创建一个空流,以便在代码的后续阶段使用。使用“ Stream.empty() ”方法,将生成一个不包含任何值的空流。当我们想在运行时忽略空指针异常时,这个空流可以派上用场。以下命令可用于相同的目的:

Stream<String> str = Stream.empty();

上面的语句将生成一个名为“str”的空流,其中没有任何元素。这可以通过使用str.count()术语检查流的计数或大小来验证。例如,

System.out.println(str.count());**
**

结果,该打印语句将输出“0”

  1. 使用带有 Stream.Builder 实例的 Stream.builder() 方法创建流

**
**

我们还可以使用 Stream Builder 在构建器的帮助下创建流。构建器基本上是一种一次构建对象的模式。让我们看看如何使用 Stream Builder 创建流的实例。

Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();

使用它会构建一个名为“numStream”的流,其中包含一些“int”元素。这是在首先创建的 Stream.Builder 实例“numBuilder”的帮助下快速完成的。

  1. 使用 Stream.of() 方法创建具有指定值的流

另一种创建流的方法是借助“ Stream.of() ”方法。这是创建具有指定值的流的简单方法。它声明并初始化流。使用“ Stream.of() ”方法创建流的示例如下:

Stream<Integer> numStream = Stream.of(1, 2, 3);

这将创建一个包含“int”元素的流,就像我们在前面的方法中所做的那样,它涉及一个“Stream.Builder”实例。在这里,我们使用具有预先指定值 [1、2 和 3] 的“Stream.of()”直接创建了一个流。

  1. 使用 Arrays.stream() 方法从现有数组创建流

创建流的另一种常用方法是使用java 中的数组。也可以使用“Arrays.stream()”方法创建流。这会从现有数组创建一个流。数组的元素全部转换为流元素。如何做到这一点的示例如下:

Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);

此代码将生成一个流“numStream”,其中包含名为“arr”的数组的内容,该数组是一个整数数组。

  1. 使用 Stream.concat() 方法组合两个现有流

另一种可用于生成流的方法是“Stream.concat()”方法。此方法用于组合两个流以创建单个流。两个流按顺序连接。这就是说第一个流先出现,然后是最后一个流中的第二个流。这种串联的示例如下:

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

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);

上面的语句将创建一个名为“combinedStream”的最终流,其中依次包含第一个流“numStream1”和第二个流“numStream2”的元素。

Java 流上的操作类型

如上所述,在 Java 8 中可以对 Java Streams 执行两种类型的操作。两大类操作是中间操作和终端操作。让我们在本节中更详细地了解它们中的每一个。

中间操作:中间操作生成输出流,仅在遇到终端操作时执行。这就是说,中间操作是延迟执行和流水线化的,只能由终端操作终止。我们将在后面的章节中阅读有关惰性求值和流水线的内容。

中间操作的一些示例是以下方法:

filter()、map()、distinct()、peek()、sorted( ) 等。

终端操作: 终端操作终止中间操作的执行,并返回最终的输出流结果。由于终端操作标志着惰性执行和流水线的结束;流一旦经过终端操作就不能再次使用。

终端操作的一些示例是以下方法:

forEach()、collect()、count()、reduce()等。

Java 流操作(示例)

中间操作

以下是一些可应用于 Java 流的中间操作的示例:

1 – filter()

此方法用于从流中过滤出与 Java 中特定谓词匹配的元素。这些被过滤的元素构成了一个新的流。让我们看一个例子来更好地理解这一点。

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);

输出:

[98]

解释:

在此示例中,可以看到使用 filter() 方法过滤偶数元素(可被 2 整除)并存储在整数列表“numStream”中,稍后打印其内容。由于 98 是流中唯一的偶数,因此它被打印在输出中。

2 – map()

此方法用于通过对原始输入流的元素执行映射函数来生成新流。新流可能具有不同的数据类型。

其示例如下:

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);

输出:

[86, 130, 2, 196, 126]

解释:

在这里,我们看到 map() 方法用于将流“numStream”的每个元素映射 2,或者将每个流元素的值加倍。此处完成的映射是乘以 2。从输出中可以看出,流中的每个元素都成功地加倍了。

3 – distinct()

此方法用于通过过滤掉重复项来仅提取流中的不同元素。其示例如下:

代码:

Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); 
List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); 
System.out.println(numList);

输出:

[43, 65, 1, 98, 63]

解释:

在这种情况下,在“numStream”上使用distinct() 方法通过从流中删除重复项来提取列表“numList”中的所有不同元素。从输出中可以看出,与最初有两个重复项(63 和 1)的输入流不同,不存在重复项。

4 – peek()

这用于在执行终端操作之前跟踪中间更改。这就是说可以使用 peek() 对流的每个元素执行操作,以生成可以在其上执行进一步中间操作的流。

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList());
System.out.println(nList);

输出:

映射:430 映射:650 映射:10 映射:980 映射:630 [430、650、10、980、630]

解释:

在这里,当 map() 方法应用于流元素时,peek() 方法用于生成中间结果。我们可以在这里观察到,甚至在应用终端操作 collect() 以在以下“print”语句中打印列表的最终内容之前,流元素的每个映射的结果本身也会预先连续打印。

5 – sorted()

sorted() 方法用于对流的元素进行排序。默认情况下,它按升序对元素进行排序。还可以将特定的排序顺序指定为参数。该方法如何实现的示例如下:

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
numStream.sorted().forEach(n -> System.out.println(n));

输出:

1 43​​ 63 65 98

解释:

这里,sorted() 方法默认用于按升序对流元素进行排序(因为没有提到具体的顺序)。输出中打印的元素可以看出是按升序排列的。

终端操作

以下是一些可应用于 Java 流的终端操作的示例:

1 – forEach()

forEach() 方法用于循环遍历流中的所有元素,并对每个元素逐个执行一个函数。这可以替代循环语句,例如“for”、“while”等。其示例如下:

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); 
numStream.forEach(n -> System.out.println(n));

输出:

43 65 1 98 63

解释:

在这里,forEach() 方法用于逐个打印流中的每个元素。

2 – count()

count() 方法用于提取流中存在的元素总数。这类似于通常用于确定集合中元素总数的 size() 方法。使用 Java 流的 count() 方法的示例如下:

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); 
System.out.println(numStream.count());

输出:

5个

解释:

由于流“numStream”包含 5 个整数元素,因此在其上使用 count() 方法时输出结果为“5”。

3 – collect()

collect() 方法用于对流元素执行可变缩减。处理完成后,它可用于从流中删除内容。它利用了

Java 中用于执行归约的收集器类。

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);

输出:

[43, 65, 1, 63]

解释:

在这个例子中,流中的所有奇数元素(不能被 2 整除)被过滤并收集/减少到一个名为“odd”的列表中。最后,打印列表“odd”。

4 – min()max()

顾名思义,min() 方法可用于流中以查找该流中的最小元素。类似地,max() 方法可用于查找流中的最大元素。让我们试着通过一个例子来理解如何使用这两者。

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);

numStream = Stream.of(43, 65, 1, 98, 63);
int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);

输出:

最小元素:1 最大元素:98

解释:

在此示例中,我们使用 min() 方法打印了流“numStream”中的最小元素,并使用 max() 方法打印了最大元素。

请注意,这里我们在应用 max() 方法之前再次将元素添加到流“numStream”。这是因为 min() 是一个终端操作,它会破坏原始流的内容,只返回最终结果(在本例中是整数“最小”)。

5 – findAny() 和 findFirst()

findAny() 返回流的任何元素作为可选。如果流为空,它将返回 Optional 返回的也将是空的。

findFirst() 返回流的第一个元素作为可选。与 findAny() 方法的情况一样,如果相关流为空,findFirst() 方法也会返回一个空的 Optional。让我们看看下面基于这些方法的例子。

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
Optional<Integer> opt = numStream.findFirst();
System.out.println(opt);
numStream = Stream.empty();
opt = numStream.findAny();
System.out.println(opt);

输出:

可选[43] 可选.空

解释:

在这里,在第一种情况下,findFirst() 方法将流的第一个元素作为 Optional 返回。接下来,当流被重新分配为空流时,findAny() 方法返回一个空的 Optional,如上文所述。

6 – allMatch()、anyMatch() 和 noneMatch()

allMatch() 方法用于检查流中的所有元素是否满足某个谓词,如果满足则返回布尔值“true”,否则返回“false”。如果流为空,则返回“true”

anyMatch() 方法用于检查流中的任何元素是否满足某个谓词。如果是,则返回“true”,否则返回“false”。如果流为空,则返回“false”。

如果没有流元素与谓词匹配,则 noneMatch() 方法返回“true”,否则返回“false”。

一个例子来说明这一点如下:

代码:

Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63);
boolean flag = numStream.allMatch(n -> n1);
System.out.println(flag);
numStream = Stream.of(43, 65, 1, 98, 63);
flag = numStream.anyMatch(n -> n1);
System.out.println(flag);
numStream = Stream.of(43, 65, 1, 98, 63);
flag = numStream.noneMatch(n -> n==1);
System.out.println(flag);

输出:

假 真 假

解释:

对于包含 1 作为元素的给定流“numStream”,allMatch() 方法返回 false,因为所有元素都不是 1,只有其中一个是;anyMatch() 方法返回 true,因为至少有一个元素为 1;noneMatch() 方法返回 false,因为 1 作为元素肯定存在于流中。


Java 流中的惰性求值

在 Java 8 中使用 Java Streams 时,惰性评估会带来显着的优化。这些基本上涉及延迟执行中间操作,直到遇到终端操作。

惰性评估负责防止在实际需要结果之前将不必要的资源浪费在计算上。

中间操作产生的输出流仅在终端操作执行后生成。惰性评估对 Java 流上的所有中间操作都有效。

在处理无限流时,惰性求值的一个非常有用的应用出现了。在无限流的情况下,在延迟评估的帮助下可以避免很多不必要的处理。

Java 流中的管道

在 Java 流的情况下,管道包括输入流、零个或多个中间操作,一个接一个地排列,最后是一个终端操作。**
**

Java Streams 中的中间操作是延迟执行的。这使得流水线化中间操作不可避免。借助管道(基本上是按顺序组合的中间操作),延迟执行成为可能。

一旦最终遇到终端操作,管道有助于跟踪需要执行的中间操作。

结论

现在让我们总结一下我们到目前为止所研究的内容。在本文中,

  1. 我们简要地了解了什么是 Java 流。
  2. 然后我们学习了许多在 Java 8 中创建 Java 流的不同方法。
  3. 接下来,我们研究了可以在 Java 流上执行的两种重要操作(中间操作和终端操作)。
  4. 然后,在此之后,我们详细地看到了中间操作和终端操作的一些示例。
  5. 最后,我们更详细地了解了惰性求值,并最终研究了 Java 流中的流水线。