持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情
上一篇文章我们介绍了Stream 的几个很重要且常用的方法“结束操作”方法,这篇文章我们继续介绍Stream 的另外几个方法。
Stream 对象常用的结束操作方法
对于Stream 来说,还有两个最常用的方法:reduece 方法和collect 方法。这篇文章我们先来介绍一下reduce 方法。
reduce 方法
reduce 方法和之前介绍的一些方法相比会复杂一些。
光看这个函数名称可能不太好猜这个函数的作用,reduce 的中文意思是“减少、降低、归纳、简化”。这个函数的作用也差不多这个意思,接下来我们看具体的讲解。
Stream 的reduce 方法用于对Stream 中的元素进行聚合求值,也正好印证了reduce 的中文意思,即“归纳,简化”。最常见的使用方法就是将Stream 中一连串的值根据自定的一些逻辑合成为单个值,比如对证书list 求和。
reduce 方法有三个重载的方法,方法定义如下:
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
下面我们分别讲一下这三个方法的作用以及如何使用。
Optional reduce(BinaryOperator accumulator)
这个方法的参数是BinaryOperator 类型的函数式编程接口实例,如果不知道BinaryOperator 的用法,可以参考这篇文章。其返回结果为Optional 类型的实例。
int result = Stream.of(1, 2, 3, 4, 5, 6, 7).reduce((first, second) -> first + second ).get();
System.out.println(result);
这个函数参数的命名是accumulator,这个单词的中文含义是“蓄电池,累加器”,我们这里肯定是取其“累加器”的意思,这样也比较直观,传入的参数的作用就是要对Stream 中的各个元素进行累加。
上面示例代码的逻辑就是对Stream 中的元素进行累加得到最终的值。
lambada 表达式中的第一个参数first 可以记作是表达式的执行结果的缓存,也就是说在表达式执行的时候,前一次的执行结果会被作为接下来执行的参数。
对于第二个参数second 来说,它代表Stream 中每个元素。
如果表达式是第一次被执行,那么first 则是stream 中的第一个元素,second 就是Stream 中的第二个元素。
如果Stream 只有一个元素,那么就直接输出结果。
参考如下代码:
Stream<String> stream = Stream.of("abc", "def", "ghi");
String result = stream.reduce(new BinaryOperator<String>() {
@Override
public String apply(String s, String s2) {
System.out.println(s);
System.out.println(s2);
System.out.println("===");
return s + s2;
}
}).get();
System.out.println(result);
}
输出结果为:
abc
def
===
abcdef
ghi
===
abcdefghi
参考上面代码,读者应该可以很直观地看出reduce 的这个重载方法的执行过程了。
T reduce(T identity, BinaryOperator accumulator);
这个重载方法的返回值类型和传入参数类型一致。
方法传参有两个参数,第一个参数是identity,identity 的中文意思是“身份,本体;特性;同一性;恒等式”。观察第二个参数我们可以发现,它和第一个重载方法的参数一致。
第一个重载方法和这个重载方法的区别是在它首次执行时,BinaryOperator 的第一个参数并不是stream 中的第一个元素,而是由参数identity`来指定的。
也就是说,我们可以通过identity 参数向reduce 方法中传入一个“初始值”。这里举例如下:
public static void main(String[] args) {
Stream<String> stream = Stream.of("abc", "def", "ghi");
String result = stream.reduce("example: ", new BinaryOperator<String>() {
@Override
public String apply(String s, String s2) {
System.out.println(s);
System.out.println(s2);
System.out.println("===");
return s + s2;
}
});
System.out.println(result);
}
输出结果为:
example:
abc
===
example: abc
def
===
example: abcdef
ghi
===
example: abcdefghi
通过示例代码我们就可以看出这个重载方法的作用及其使用了。已经讲过的两种重载方法的差别就是这个比第一种多了一个初始值。
另外我们也会发现,两种重载方法的返回值类型不一样。上一篇文章讲的重载方法的返回值是由Optional 包装的,但是本篇文章的重载方法时直接返回传入参数相同类型的值的。
这是因为存在Stream 为空的情况。如果Stream 为空,那么reduce 方法无法得到一个计算结果,所以结果就会为null,所以在上一篇文章的重载方法并不直接返回计算的结果,而是将计算结果用Optional 进行包装。我们可以通过返回的Optional 实例的get 方法获得一个传入类型的结果。
而第二种实现因为需要传入初始值,所以即使Stream 为空,也不会出现返回结果为null 的情况;在Stream 为空的时候,reduce 就可以直接把初始值返回。
U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner);
先看这个方法定义,我们就知道它的用法对比前两种会更复杂。
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
其实我们不难发现,前两种实现的计算结果的类型必须和Stream 中的元素类型相同,比如说Stream 中的类型为String,那么计算结果的类型也必须为String。这个很明显的问题就是方法的灵活性的不足,有可能无法完成某些任务,比入我们要对一个int 类型的list 转为String 类型,就要用到这个reduce 的实现了。这个实现不会将执行结果的类型与Stream 中元素的类型绑死。
接下来我们分析这个实现的三个参数。
第一个参数为identity,返回类型为U,这个参数传递的是你要返回的U 类型对象的初始化实例。它的作用就是传入一个初始值。
第二个参数是累加器accumulator,类型为BiFunction,不同于reduce 的第二个重载方法的实现。它的作用就是声明你在U 类型的值上累加你的数据来源T 类型的值的逻辑,例如(u,t)->u.add(t),此时lambda 表达式的参数列表是返回实例u和遍历的集合元素T,函数体的实现是在U上累加T。
第三个参数组合器combiner,类型是BinaryOperator,它的中文意思是“组合器”,其作用是在Stream 支持并发操作的时候,为了避免竞争,对于reduce 的每个线程都会有独立的计算结果,第三个combiner 的作用在于合并每个线程的执行结果从而得到最终结果。这也说明了为什么第三个函数参数的数据类型必须和reduce 返回的数据类型相同。但是在日常开发的过程中,如果我们没有用多线程的Stream,这个参数随便传一个BinaryOperator 的实现就可以了,但是不能传null。
我们就假设一个场景,在不考虑并行Stream 的情况下,将元素为int 类型的list 转为String 元素类型的list,接下来我们看示例代码:
public static void main(String[] args) {
ArrayList<String> result = Stream.of(1, 2, 3, 4).reduce(new ArrayList<String>(), (u, t) -> {
u.add(t.toString() + "ele");
return u;
}, (a, b) -> null);
System.out.println(result);
}
输出结果为:
[1ele, 2ele, 3ele, 4ele]
总结
至此,reduce 方法的三个重载实现就讲完了,读者可以多多复习,多多实践,加深理解和记忆。接下来我们介绍collect 方法。