Java8提供的Stream API,将对数据流的所有操作,仅用三个步骤概括全了-过滤、转化、归约。其中,过滤、转化还比较容易理解,但是归约就是一个非常高级的抽象接口了,这篇博客从一个简单的累加例子出发,管中窥豹,带你彻底理解归约器。
何谓归约
public class TestStream { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); int[] sum = numbers.stream() .filter(i -> i > 0) .collect(() -> new int[]{0}, (a, b) -> a[0] += b, (s1, s2) -> s1[0] += s2[0]); System.out.println(sum[0]); } }这是一个简单的先过滤非正整数,然后对剩余元素求和的例子(这个例子纯粹是为了说明Collector原理所写,等你熟悉了Stream,你会有更好的实现方法)。你看,那么长一串数字流最后变成了一个数—被归约了,是不是很形象~~
Collector接口
public interface Collector<T, A, R> { Supplier<A> supplier(); BiConsumer<A, T> accumulator(); BinaryOperator<A> combiner(); Function<A, R> finisher(); Set<Characteristics> characteristics(); }先强行记住上面四个方法(最后一个先不管),根据他们的名字(其实都很形象),看能不能与我下面的描述对应起来
由特殊到一般
我们来从头开始梳理计算从1-9累加和的步骤
1.首先你得提供一个用来接收每一步累加结果的变量,我们用A表示
2.你得确定你的累加变量的初始值是什么。如果我们把计算范围看作一个变量,那这一步就非常有必要了,我
现在给你的计算区间是[1,100),那如果我给你一个[0,0)这样一个区间呢,这个数据流是空的,但你同样要有一个
输出
3.确定你的操作。为了理解这个高级抽象接口的参数意义,我们不得不尽可能把一切都看成可变的,这里这个累加
的操作也是可变的,比如说我想求阶乘了
4.纯逻辑上的抽象已经我们已经做到极致了,但你还可以做的更完美,让我们上升到物理层面上去思考。假如
我想把这个工作分给三台计算机去做,另外一台计算机专门负责收集计算结果。先假设每一台计算机的累加结果
都用A表示?那么负责合并的计算机该怎么把所有的结果A合并起来,这也是个可变的操作
5.想一想还能有什么会是变化的。让我们接着上面的思路,汇总计算机把所有的计算结果都汇总好了,汇总的
结果还是一个A类型的。假设是累加的例子,那么它就是一个int,现在我想要的结果不是一个int了,我想知道
这个值是不是大于5000,那么结果就是一个boolean类型,所以我们还可以抽象出一个结果转换器,来对累加
结果进行转换,转换成我们想要的最终结果
你把上面的步骤,与API中的抽象方法都对应起来了吗?下面开始划重点啦(敲黑板^^^)
- 对于步骤1和2,我们需要一个Supplier<A>,它可以创建一个用于归约过程的累加器,这个累加器的类型是A。再回到开头的那个例子,()->new int[]{0},这个lambda表达式就是一个累加器啦,它创建出一个数组用来接收累加的结果,并且给定初始值为0
- 对于步骤3,我们需要一个BiConsumer<A,T>,这是一个二操作数的消费器,A是累加器,T是数据流中的每个元素的数据类型,可以看作,A把T累加到自己头上,这个T就是被消费了。(a,b)->a[0]+=b,就是一个BiConsumer
- 对于步骤4,我们需要一个BinaryOperator<A>,它其实是一个BiFunction<T,U,R>,后者的作用是把T和U合并为一个R,所以BinaryOperator<A>的作用是,把两个A合并为一个A。(s1,s2)->s1[0]+=s2[0],就是一个BinaryOperator
- 对于步骤5,我们需要一个Function<A,R>,这就是Stream API中最基础的一个转换器,它的作用是把A转换为R。开头的那个例子没有体现出来,这个Function可能是这样的,a[0]->a[0]>5000。可以把这个Function包装到Collector中,传递给collect方法,就可以实现转化了
至于API中的最后一个Set<Characteristics>是干嘛的,它定义了归约器的一些其他行为,比如流是否可以并行归约,是否可以直接把累加器类型作为最终返回值返回等
思维发散
再比如说,我把工作分摊到每一台计算机的策略是什么,是平均分摊?还是先获取每一台计算机的硬件数据,根据它们的计算能力决定它们分配任务的多少?(这个可以放到底层实现去做,放到这个接口里似乎不合适,这里只是提供一个发散思维的思考方向)