0%

8.2.6 使用Java8新增的Stream操作集合

8.2.6 使用Java 8新增的Stream操作集合

Java 8还新增了StreamIntStreamLongStreamDoubleStream等流式API,这些API代表多个支持串行并行聚集操作的元素。上面4个接口中, Stream是一个通用的流接口,而IntStreamLongStreamDoubleStream则代表元素类型为intlongdouble的流.

如何创建类型为int,long,double的Stream

Java 8还为上面每个流式API提供了对应的Builder,例如Stream.BuilderIntStream.BuilderLongStream.BuilderDoubleStream.Builder,开发者可以通过这些Builder来创建对应的流.

独立使用Stream的步骤

独立使用Stream的步骤如下:

  1. 使用StreamXxxStreambuilder()类方法创建该Stream对应的Builder.
  2. 重复调用Builderadd()方法向该流中添加多个元素。
  3. 调用Builderbuild()方法获取对应的Stream
  4. 调用Stream的聚集方法。

在上面4个步骤中,第4步可以根据具体需求来调用不同的方法, Stream提供了大量的聚集方法供用户调用,具体可参考StreamXxxStreamAPI文档。
对于大部分聚集方法而言,每个Stream只能执行一次

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import java.util.stream.*;

public class IntStreamTest {
public static void main(String[] args) {
IntStream intStream = IntStream.builder().add(20).add(13).add(-2).add(18).build();
// 下面调用聚集方法的代码每次只能执行一个
// System.out.println("流中的最大值:" + intStream.max().getAsInt());
// System.out.println("流中的最小值:" + intStream.min().getAsInt());
// System.out.println("流中所有元素的总和:" + intStream.sum());
// System.out.println("流中所有元素的总数:" + intStream.count());
// System.out.println("流中所有元素的平均值:" + intStream.average());
// System.out.println("流中所有元素的平方是否都大于20:" + intStream.allMatch(ele -> ele * ele > 20));
// System.out.println("流中是否包含任何元素的平方大于20:" + intStream.anyMatch(ele -> ele * ele > 20));

// 将is映射成一个新Stream,新Stream的每个元素是原Stream元素的2倍+1
IntStream newIs = intStream.map(ele -> ele * 2 + 1);
// 使用方法引用的方式来遍历集合元素
newIs.forEach(System.out::println); // 输出41 27 -3 37
}
}

上面程序先创建了一个IntStream,接下来分别多次调用IntStream的聚集方法执行操作,这样即可获取该流的相关信息。注意:IntStream的方法每次只能执行一个,因此需要把其他代码注释掉。

中间方法和末端方法

Stream提供了大量的方法进行聚集操作,这些方法既可以是”中间的”(intermediate),也可以是”末端的”(terminal)。

  • 中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面程序中的map()方法就是中间方法。中间方法的返回值是另外一个流.
  • 末端方法:末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被”消耗”且不再可用。上面程序中的sum()count()average()等方法都是末端方法。

流的方法有什么特征

除此之外,关于流的方法还有如下两个特征。

  • 有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销。
  • 短路方法:短路方法可以尽早结束对流的操作,不必检査所有的元素。

Stream常用的中间方法

下面简单介绍一下Stream常用的中间方法。

Stream中间方法 描述
filter(Predicate predicate) 过滤Stream中所有不符合predicate的元素。
mapToXxx((ToXxxFunction mapper) 使用ToXxxFunction对流中的元素执行一对一的转换,该法返回的新流中包含了ToXxxFunction转换生成的所有元素。
peek(Consumer action) 依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的素。该方法主要用于调试.
distinct() 该方法用于排序流中所有重复的元素(判断元素重复的标准是使 equals()比较返回true )这是一个有状态的方法
sorted() 该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
limit(long maxSize) 该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。
## Stream常用的末端方法 ##
下面简单介绍一下Stream常用的末端方法。
Stream末端方法 描述
forEach(Consumer action) 遍历流中所有元素,对每个元素执行 action
toArray() 将流中所有元素转换为一个数组。
reduce() 该方法有三个重载的版本,都用于通过某种操作来合并流中的元素.
min() 返回流中所有元素的最小值。
max() 返回流中所有元素的最大值。
count() 返回流中所有元素的数量。
anyMatch(Predicate predicate) 判断流中是否至少包含一个元素符合 Predicate条件。
allMatch(Predicate predicate) 判断流中是否每个元素都符合 Predicate条件。
noneMatch(Predicate predicate) 判断流中是否所有元素都不符合 Predicate条件
findFirst() 返回流中的第一个元素。
findAny() 返回流中的任意一个元素。

如何获取集合对应的流

Java 8允许使用流式API来操作集合, Collection接口提供了一个stream()默认方法,该方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素。由于Stream可以对集合元素进行整体的聚集操作,因此Stream极大地丰富了集合的功能。

实例 使用流操作集合

例如,对于8.2.5节介绍的示例程序,该程序需要额外定义一个calAll()方法来遍历集合元素,然后依次对每个集合元素进行判断——这太麻烦了。如果使用Stream,即可直接对集合中所有元素进行批量操作。下面使用Stream来改写这个程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

import java.util.Collection;
import java.util.HashSet;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class CollectionStream {
public static void main(String[] args) {
// 创建books集合、为books集合添加元素的代码与8.2.5小节的程序相同。
Collection<String> collection = new HashSet<>();
collection.add("C");
collection.add("C++");
collection.add("Java");
collection.add("JavaScirpt");
collection.add("Python");
// 先获取包含字母C的流,
Stream<String> strContainsC = collection.stream().filter(str -> str.contains("C"));
// 输出流中元素的长度
System.out.println("包含字符C的元素个数:" + strContainsC.count());
// 简写形式:
System.out.println("以字符J开头的元素个数:" + collection.stream().filter(str -> str.startsWith("J")).count());
System.out.println("---------------------------");
collection.forEach(str -> System.out.print(str + "\t"));
System.out.println();
// 先调用Collection对象的stream()方法将集合转换为Stream,
// 再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream
IntStream intStream = collection.stream().mapToInt(str -> str.length());
// 调用forEach()方法遍历IntStream中每个元素,注意没有顺序
intStream.forEach(intv -> System.out.print(intv + "\t"));
}
}

运行效果:

1
2
3
4
5
包含字符C的元素个数:2
以字符J开头的元素个数:2
---------------------------
Java C++ C JavaScirpt Python
4 3 1 10 6

从上面程序中的代码可以看出,

程序只要调用Collectionstream()方法即可返回该集合对应的Stream,接下来就可通过Stream提供的方法对所有集合元素进行处理,这样大大地简化了集合编程的代码。
上面程序中最后一段粗体字代码:

1
2
3
IntStream intStream = collection.stream().mapToInt(str -> str.length());
// 调用forEach()方法遍历IntStream中每个元素,注意没有顺序
intStream.forEach(intv -> System.out.print(intv + "\t"));

先调用Collection对象的stream()方法将集合转换为Stream对象,然后调用Stream对象的mapToInt()方法将其转换为IntStream——这个mapToInt()方法就是一个中间方法,因此程序可继续调用IntStreamforEach()方法来遍历流中的元素。

原文链接: 8.2.6 使用Java8新增的Stream操作集合