不像reduce操作,每处理一个元素就会产生一个新值,collect方法只更新已有的值。
还是假设要求背包的平均重量,你需要哪些值?总重量和总个数。你可以新建一个数据类型包含并追踪这两个变量。
class Averager implements IntConsumer { private int total = 0; private int count = 0; public double average() { return count > 0 ? ((double) total)/count : 0; } public void accept(int i) { total += i; count++; } public void combine(Averager other) { total += other.total; count += other.count; } }
IntCousumer接口接受一个int类型的参数,不返回任何值。Averager的accept是重写的。
下面就是管道如何使用Averager和collect完成求平均值。
Averager average = packages.stream() .map(Package::getWeight) .collect(Averager::new, Averager::accept,Averager::combine)
这里使用的是函数引用,没有用Lambda表达式。注意,collect操作的容器必须是可变的。
先看collect的函数原型,
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combine)
第一个参数Supplier表示结果集的装配类型。BiConsumer的接受两个参数,不返回结果的操作。
第二个参数和第三个参数都要是“可结合的”,“无状态的”,"非干涉的"。分别是“把一个元素加入结果”,“组合两个结果(就是一个merger过程)”。
“可结合性”前一篇文章已说明了。这里对后两个性质说明一下。
一个有状态的Lambda表达式是指其结果依赖于任何在管道执行中可能改变的状态。这个说法有点拗口,其英文原话是:
A stateful lambda is one whose result depends on any state which might change during the execution of the stream pipeline
下面在map里的函数就是有状态的,
Set<Integer> seen = Collections.synchronizedSet(new HashSet<>()); stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
对于相同的输入,对多线程并发操作时,结果可能是不同的。
流操作允许你执行并行计算,即使是对ArrayList那样的非线程安全集合。这就要求我们要防止数据源的干涉。对大多数数据源,防止干涉就是指确保在管道操作时不能修改。有个例外是,当数据源是并行集合。注意,要求“非干涉”对所有管道都适合,不只是对并行计算。除了已经是并行集合(Current Collection),在执行时修改数据源是会导致异常的,或是错误的结果。对一个行为良好的流,对数据源的修改应该反映到所涉及的元素。
List<String> l = new ArrayList(Arrays.asList("one", "two")); Stream<String> sl = l.stream(); l.add("three"); String s = sl.collect(joining(" "));
我们去修改l,最后sl还是会变化的,只要这个修改发生在终结操作前。
它等价于,
R result = supplier.get(); for (T element : this stream) accumulator.accept(result, element); return result;
再来一个例子,完成字符串拼接,
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString();
collect操作最适合集合操作。下面是把所有背包的重量放入一个List<Integer>集合,
List<Integer> weights = packages.stream() .mapToInt(p -> p.getWeight()) .collect(Collectors.toList());
这个版本的collect只有一个参数Collector,这个类封装了collect所需要的supplier,accumulator和combine。
Collectors包含很多有用的reduction操作,并都返回Collector。
上面的toList方法,会累积流的元素到一个新的List实例。
假设背包是由颜色的,有color属性,并有setter和getter。我们按颜色给它们分类:
Map<String color,List<Package> byColor = packages.stream() .collect( Collectors.groupingBy(Person::getColor) );