Stream流常见方法浅析

Stream流常见方法浅析

flatMap

flatMap方法用于将流中的每个元素转换成其他类型元素的流,比如,当前有一个订单(Order)列表,每个订单又包含多个商品(itemList),如果要得到所有订单的所有商品汇总,就可以使用该方法,如下:

1
Stream<Item> allItemStream = orderList.stream().flatMap(order -> order.itemList.stream());

distinct

distinct方法用于对流中的元素去重,判断元素是否重复使用的是equals方法

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 0, 1, 2, 2, 3);
//不重复的数字流,uniqueNumStream -> (-2, -1, 0, 1, 2, 3)
Stream<Integer> uniqueNumStream = numStream.distinct();

sorted

sorted有一个无参和一个有参的方法,用于对流中的元素进行排序。无参方法要求流中的元素必须实现Comparable接口,不然会报java.lang.ClassCastException异常

1
2
3
Stream<Integer> unorderedStream = Stream.of(5, 6, 32, 7, 27, 4);
//按从小到大排序完成的流,orderedStream -> (4, 5, 6, 7, 27, 32)
Stream<Integer> orderedStream = unorderedStream.sorted();

有参方法sorted(Comparator<? super T> comparator)不需要元素实现Comparable接口,通过指定的元素比较器对流内的元素进行排序

1
2
3
Stream<String> unorderedStream = Stream.of("1234", "123", "12", "12345", "123456", "1");
//按字符串长度从小到大排序完成的流,orderedStream -> ("1", "12", "123", "1234", "12345", "123456")
Stream<String> orderedStream = unorderedStream.sorted(Comparator.comparingInt(String::length));

peek

peek方法可以不调整元素顺序和数量的情况下消费每一个元素,然后产生新的流,主要是用于对流执行的中间过程做debug的时候使用,因为Stream使用的时候一般都是链式调用的,所以可能会执行多次流操作,如果想看每个元素在多次流操作中间的流转情况,就可以使用这个方法实现

1
2
3
4
5
6
7
8
9
10
11
12
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());

输出:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

skip(long n)

skip方法用于跳过前n个元素,如果流中的元素数量不足n,则返回一个空的流

1
2
3
Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//跳过前3个元素,subStringStream -> ("1", "2", "3")
Stream<String> subStringStream = stringStream.skip(3);

forEachOrdered

forEachOrdered方法可以保证顺序遍历,比如这个流是从外部传进来的,然后在这之前调用过parallel方法开启了多线程执行,就可以使用这个方法保证单线程顺序遍历

1
2
3
4
5
6
7
8
Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//顺序遍历输出元素
//单线程操作流的时候它与forEach()是一致的。
stringStream.forEachOrdered(System.out::println);
stringStream.forEach(System.out::println);
//多线程遍历输出元素,下面这行跟上面的执行结果是一样的
stringStream.parallel().forEachOrdered(System.out::println);

reduce

reduce有三个重载方法,作用是对流内元素做累进操作

第一个reduce(BinaryOperator<T> accumulator)

accumulator 为累进操作的具体计算

单线程等价如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//查找最小值
Optional<Integer> min = numStream.reduce(BinaryOperator.minBy(Integer::compareTo));
//输出 -2
System.out.println(min.get());

//过滤出大于5的元素流
numStream = Stream.of(-2, -1, 0, 1, 2, 3).filter(num -> num > 5);
//查找最小值
min = numStream.reduce(BinaryOperator.minBy(Integer::compareTo));
//输出 Optional.empty
System.out.println(min);

第二个reduce(T identity, BinaryOperator<T> accumulator)

identity 为累进操作的初始值
accumulator 同上

单线程等价如下代码

1
2
3
4
5
6
7
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//累加计算所有元素的和,sum=3
int sum = numStream.reduce(0, Integer::sum);

第三个reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

identityaccumulator同上
combiner用于多线程执行的情况下合并最终结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
int sum = numStream.parallel().reduce(0, (a, b) -> {
System.out.println("accumulator执行:" + a + " + " + b);
return a + b;
}, (a, b) -> {
System.out.println("combiner执行:" + a + " + " + b);
return a + b;
});
System.out.println("最终结果:"+sum);

输出:
accumulator执行:0 + -1
accumulator执行:0 + 1
accumulator执行:0 + 0
accumulator执行:0 + 2
accumulator执行:0 + -2
accumulator执行:0 + 3
combiner执行:2 + 3
combiner执行:-1 + 0
combiner执行:1 + 5
combiner执行:-2 + -1
combiner执行:-3 + 6
最终结果:3

collect

collect有两个重载方法,主要作用是把流中的元素作为集合转换成其他Collection的子类,其内部实现类似于前面的累进操作

第一个collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)

supplier 需要返回开始执行时的默认结果
accumulator 用于累进计算用
combiner 用于多线程合并结果

单线程执行等价于如下代码

1
2
3
4
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;

第二个collect(Collector<? super T, A, R> collector)

Collector其实是对上面的方法参数的一个封装,内部执行逻辑是一样的,只不过JDK提供了一些默认的Collector实现,对于

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
List<Integer> numList = numStream.collect(Collectors.toList());
Set<Integer> numSet = numStream.collect(Collectors.toSet());

anyMatch

anyMatch方法用于匹配校验流内元素是否有符合指定条件的元素

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判断是否包含正数,hasPositiveNum=true
boolean hasPositiveNum = numStream.anyMatch(num -> num > 0);

allMatch

allMatch方法用于匹配校验流内元素是否所有元素都符合指定条件

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判断是否全部是正数,allNumPositive=false
boolean allNumPositive = numStream.allMatch(num -> num > 0);

noneMatch

noneMatch方法用于匹配校验流内元素是否都不符合指定条件

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判断是否没有小于0的元素,noNegativeNum=false
boolean noNegativeNum = numStream.noneMatch(num -> num < 0);

findFirst

findFirst方法用于获取第一个元素,如果流是空的,则返回Optional.empty

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//获取第一个元素,firstNum=-2
Optional<Integer> firstNum = numStream.findFirst();

findAny

findAny方法用于获取流中的任意一个元素,如果流是空的,则返回Optional.empty,因为可能会使用多线程,所以不保证每次返回的是同一个元素

1
2
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
Optional<Integer> anyNum = numStream.findAny();