在Stream的接口中,最强大的接口就是collect了,当然这也是最难的接口,它对Stream的应用过程可分为四步:
1. 利用Supplier接口,创建存储中间运算结果的容器(不是返回结果),扮演的角色为supplier;
2. 利用BiConsumer
// 声明单词集合对象
List<String> words = Lists.newArrayList("hi", "Hello", "Hi", "hello", "hI");
// 定义Supplier接口,存储中间运算结果
Supplier<Map<String, Integer>> supplier = () -> new HashMap<String, Integer>();
// 定义累加器
BiConsumer<Map<String, Integer>, String> accumulator = (t, u) -> {
String lower = u.toLowerCase();
// 依次添加元素
Integer num = t.get(lower);
if(num == null) {
num = 1;
} else {
num = num + 1;
}
t.put(lower, num);
};
// 并行会对输入数据进行切割,会创建利用Supplier与BiConsumer的多个实例,所以需要进行合并
BinaryOperator<Map<String, Integer>> combiner = (a, b) -> {
Map<String, Integer> result = Maps.newHashMap(a);
for(String key : b.keySet()) {
Integer num = b.get(key) == null ? 0 : b.get(key);
Integer _num = result.get(key) == null ? 0 : result.get(key);
result.put(key, num + _num);
}
return result;
};
// 这一个功能只是为了把所有的步骤走完
Function<Map<String, Integer>, Integer> finisher = t -> t.keySet().size();
//
Collector<String, Map<String, Integer>, Integer> collector = Collector.of(supplier, accumulator, combiner, finisher);
// 并行执行
Integer wc = words.stream().parallel().collect(collector);
System.out.println(wc);
为了提高对输入数据的处理速度,我们都会采用并行计算的方式以加强CPU的负载,此时请务必对数据进行合并,否则将会出现错误的结果。
从上面的例子可以看出,中间结果容器贯穿始终,这也使我们花费了大量的代码在描述此容器,并且代码散落在各处,复用性较差,为了解决此问题,我们可以将中间结果容器融入到对象中,充分利用对象的上下文,大幅简化方法的签名。
在展示封装代码之前,我们再强调一遍Collector
static class WordCounter {
private Map container = new HashMap<>();
// 与接口BiConsumer的accept一致,省略A参数,无返回值,承担accumulator角色
public void add(String world) {
String lower = world.toLowerCase();
Integer num = container.get(lower);
if(num == null) {
num = 1;
} else {
num = num + 1;
}
container.put(lower, num);
}
// BinaryOperator的apply()一致,依旧省略一个A参数,返回类型为A,承担combiner角色
public WordCounter merge(WordCounter other) {
Map result = Maps.newHashMap(container);
for(String key : other.container.keySet()) {
Integer num = other.container.get(key) == null ? 0 : other.container.get(key);
Integer _num = container.get(key) == null ? 0 : container.get(key);
result.put(key, num + _num);
}
this.container = result;
return this;
}
// 返回结果,与Function的apply()一致,依旧省略一个A参数,返回类型为R,承担finisher完成器角色
public int count() {
for(String key : container.keySet()) {
System.out.println(key + ":" + container.get(key));
}
return this.container.size();
}
}
现在我们可以利用封装的类及其方法提炼出更简单的写法,如下:
// 请仔细注意收集器的泛型顺序
Collector coll = Collector.of(WordCounter::new, WordCounter::add, WordCounter::merge, WordCounter::count);
Integer counter = words.stream().parallel().collect(coll);
System.out.println(counter);
收集器(Collector)相当于是对其他函数功能的一个合集,如map、reduce、consume、merge、supply等,只有掌握了此方法,才算真正掌握了Stream操作,在简化与提炼的过程中,我们可以把中间结果容器融入到对象的上下文中,一是增加了代码的复用性,二是提高了代码的封装性。