Java Stream

Stream概述

StreamJava8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂查找、过滤和映射数据等操作。我们通常借助Stream API对集合数据进行操作。

简单说一下使用Stream时会发生的步骤:

1
获取一个数据源 -> 数据转换 -> 执行操作获取结果
  1. 获取一个数据源

    Stream本身并不会存储数据,而是对数据源的一种抽象视图,它更像一个管道。许哟啊注意的是,Stream并不会改变原始数据。

    这里管道可以如何理解呢?就好比生产线上流动的传送带,Stream负责对这些材料进行加工处理。

    1
    2
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = list.stream(); // 创建数据源
  2. 数据转换

    数据转换通常会借助一些中间操作,例如:

    • map:将元素映射为新值。

    • filter:过滤掉不符合条件的元素。

    • 等等…

    1
    Stream<Integer> NewStream = stream.filter(n -> n % 2 == 0);
  3. 执行操作获取结果

    Stream的计算是惰性求值的,只有在执行终端操作时才会进行实际计算,例如:

    • collect:收集结果到集合。
    • forEach:遍历。
    • 等等…
    1
    List<Integer> result = NewStream.collect(Collectors.toList());

Stream使用

流的创建操作

1、 Stream可以通过Collection创建。

1
2
3
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

这里解释一下streamparallelStream的区别:

  • stream是顺序流,它是依照主线程的顺序对流进行操作。
  • parallelStream是并行流,依照多线程并行执行的方式对流进行操作,因此不保证顺序。

2、Stream可以通过Array创建。

1
2
int[] array = { 1, 3, 5, 6, 8 };
IntStream stream = Arrays.stream(array);

3、Stream可以通过静态方法:of()、iterate()、generate()创建。

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

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);

Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

流的中间操作

筛选

筛选会按照一定规则校验流中的元素,将符合条件的元素提取至新的流中,常见的操作如下:

  • filter():根据条件过滤。
  • limit(n):获取n个元素。
  • skip(n):跳过n个元素。
  • distinct():去除重复元素。
1
2
3
4
5
6
7
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);

Stream<Integer> newStream = stream.filter(s -> s > 5) // 6 6 7 9 8 10 12 14 14
.distinct() // 6 7 9 8 10 12 14
.skip(2) // 9 8 10 12 14
.limit(2); // 9 8
newStream.forEach(System.out::println);

映射

映射可以将一个流的元素按照一定的映射规则映射到另一个流中。分为mapflatMap

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> list = Arrays.asList("a,b,c", "1,2,3");

//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123

Stream<String> s3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3

流的终止操作

遍历

1
2
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
stream.forEach(System.out::println);

匹配

Stream中的元素是以Optional类型存在的。

1
2
3
4
5
6
7
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

Optional<Integer> findFirst = list.stream().filter(x -> x > 3).findFirst();
Optional<Integer> findAny = list.parallelStream().filter(x -> x > 3).findAny();

System.out.println("匹配第一个值:" + findFirst.get());
System.out.println("匹配任意一个值:" + findAny.get());

输出结果为

1
2
匹配第一个值:4
匹配任意一个值:4

归约 reduce

通常分为三类:

Optional reduce
1
Optional<T> reduce(BinaryOperator<T> accumulator)

第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。

示例:

1
2
3
4
5
6
7
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

// 求和
Optional<Integer> sum = list.stream()
.reduce((a, b) -> a + b);

System.out.println(sum.get()); // 输出 15
T reduce
1
T reduce(T identity, BinaryOperator<T> accumulator)

流程基本一致,不过第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。

1
2
3
4
5
6
7
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

// 带初始值的求和
int sum = list.stream()
.reduce(0, (a, b) -> a + b);

System.out.println(sum); // 输出 15
U reduce
1
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

主要用于并行流,我们知道并行流中数据会被拆分给多个线程进行执行,此时每个线程的处理流程会按照T reduce执行;第三个参数combiner,会将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行归约。

收集 Collector

顾名思义,Collector的职责就是将流中的数据重新归集到一起。

这里以toList()为例:

1
2
3
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
List<Integer> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);

查看collect方法声明:

1
2

<R, A> R collect(Collector<? super T, A, R> collector);

再根据下方toList()返回值类型可以知道当前stream.collect()的返回类型是List<T>

1
2
3
4
5
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);

可以看到toList()返回值类型为Collector<T, ?, List<T>>CollectorImpl实现了接口Collector<T, A, R>

1
static class CollectorImpl<T, A, R> implements Collector<T, A, R>

CollectorImpl实现如下:

1
2
3
4
5
6
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
  • supplier:空的累加器。
  • accumulator:累加操作。
  • combiner:合并操作。
  • characteristics:收集器特性。