Java : 聚集操作(2)

不像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)
    	);



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。