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只能执行一次

对于大部分聚集方法而言,每个Stream只能执行一次

程序 IntStream使用示例

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中间方法 描述
Stream<T> filter(Predicate<? super T> predicate) 过滤Stream中所有不符合predicate的元素。
IntStream mapToInt(ToIntFunction<? super T> mapper) 使用ToIntFunction对流中的元素执行一对一的转换,该法返回的新流中包含了ToIntFunction转换生成的所有元素。
LongStream mapToLong(ToLongFunction<? super T> mapper) Returns a LongStream consisting of the results of applying the given function to the elements of this stream.
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) Returns a DoubleStream consisting of the results of applying the given function to the elements of this stream.
Stream<T> peek(Consumer<? super T> action) 依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的素。该方法主要用于调试.
Stream<T> distinct() 该方法用于排序流中所有重复的元素(判断元素重复的标准是使 equals()比较返回true )这是一个有状态的方法
Stream<T> sorted() 该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
Stream<T> sorted(Comparator<? super T> comparator) Returns a stream consisting of the elements of this stream, sorted according to the provided
Stream<T> limit(long maxSize) 该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。

Stream常用的末端方法

下面简单介绍一下Stream常用的末端方法。

方法 描述
void forEach(Consumer<? super T> action) 遍历流中所有元素,对每个元素执行action

将流转成数组

方法 描述
Object[] toArray() 将流中所有元素转换为一个数组。
<A> A[] toArray(IntFunction<A[]> generator) Returns an array containing the elements of this stream, using the provided generator function to allocate the returned array, as well as any additional arrays that might be required for a partitioned execution or for resizing.

合并流中的元素:reduce方法

该方法有三个重载的版本,都用于通过某种操作来合并流中的元素.

方法 描述
Optional<T> reduce(BinaryOperator<T> accumulator) Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any.
T reduce(T identity, BinaryOperator<T> accumulator) Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value.
<U> U reduce(U identity, BiFunction<U,​? super T,​U> accumulator, BinaryOperator<U> combiner) Performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions.

求最大值 最小值 统计流中元素数量

方法 描述
Optional<T> max(Comparator<? super T> comparator) 返回流中所有元素的最大值
Optional<T> min(Comparator<? super T> comparator) 返回流中所有元素的最小值
long count() 返回流中所有元素的数量。

判断流中的元素是否符合Predicate条件

方法 描述
boolean allMatch(Predicate<? super T> predicate) 判断流中是否每个元素都符合Predicate条件。
boolean anyMatch(Predicate<? super T> predicate) 判断流中是否至少包含一个元素符合Predicate条件。
boolean noneMatch(Predicate<? super T> predicate) 判断流中是否所有元素都不符合Predicate条件

返回流中的元素

方法 描述
Optional<T> findFirst() 返回流中的第一个元素。
Optional<T> findAny() 返回流中的任意一个元素。

获取集合对应的流

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

Collection接口的stream方法 描述
default Stream<E> stream() Returns a sequential Stream with this collection as its source.

程序 使用流操作集合

例如,对于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
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()方法来遍历流中的元素。