首页 > 代码库 > Java:聚集操作

Java:聚集操作

你使用集合的目的是什么?当然不是为了简单的存储然后置之不理,你是为了从集合取数据和操作数据的。

再次考虑前一篇文章提到的背包类,

如果要打印所有背包的重量,

for(Package p : packages)
    System.out.println(p.getWeight());

遍历,可以使用"forEach"这一聚集操作,

packages.stream()
             .forEach(e -> System.out.println(p.getWeigh());

看不懂语法不要紧。我们再来个稍微复杂一点的,

我们只打印“重量大于20的背包重量”,

packages.stream()
             .filter(e -> e.getWeight() > 20)
             .forEach(e - > System.out.println(e.getWeight());

分析其结构,

packages是个集合,stream()方法是获取其“流”,能产生流的不只是集合,数组,I/O通道都可以。下面就是管道操作(和MongoDB或是Linux下的管道类似)了。即有0个或若干个中间操作,如这里的filter,来产生新的流,然后有一个终结操作,它可以有返回值(不再是一个流),也可以没有返回值。

其中,filter的函数原型是:

Stream<T> filter<Predicate<? super T> p)
接受一个谓词参数(条件)。

查看java.util.strem下面的API,其操作时很多的,部分如下,



具体用法不一一介绍。

我们再看一个需求,求“重量大于20的背包的平均重量”。

double average = packages
       .stream()
       .filter(e -> e.getWeight() > 20)
       .mapToInt(e -> e.getWeigh())
       .average()
       .getAsDouble();

除了filter表示过滤,还有maptoInt,返回一个IntStream。

函数原型是,

IntStream maptoInt(toIntFunction<? super T> mapper)
其参数是一个返回Int的lambda表达式。

average()是IntStream的方法,很明显,是取平均值的,返回一个OptionalDouble,你可以回想OptionalDouble是什么?怎么不直接返回Double!!OptionalDouble是java8新加的,类似的API很多,它的特点是该对象可能包含也可能不包含一个值。作用在于:传统方法返回一个为Null的值,继续操作会报NullException,如果OptionalXX没有值,是可以继续操作的(当然没有值getAsDouble会报NoSuchElement异常。


另外,average这个终结操作也是reduction 操作。

在JDK里面,像average,sum,min等都是通过组合流的内容返回一个值,这些操作称为reduction操作。当然,有点reduction操作返回一个集合而不是一个值。值得一提的是,JDK还提供了两个一般化的操作:reduce和collect。


假设我们需要对背包容量求和,

Integer total = packages
        .stream()
        .mapToInt(e -> e,getWeight())
        .sum();


如果要用reduce对它改造,

Integer total = packages
        .stream()
        .mapToInt(e -> e,getWeight())
        .reduce(0,(a,b) -> a + b);


如果使用函数引用,

Integer total = packages
        .stream()
        .mapToInt(e -> e,getWeight())
        .reduce(0,Integer::sum);

查看reduce的函数原型:

T reduce(T indentidy,BinaryOperator<T> accumulator)

这可能比较费解,

API是这样介绍的,using the provided identity value and an associative accumulation function。

先看是associative,“结合性”,就是说要满足,

a op b op c = a op (b op c)

像加法,最小值,最大值,字符拼接都有这个性质。


API 还说,reduce函数等价于,

 T result = identity;
     for (T element : this stream)
         result = accumulator.apply(result, element)
     return result;

那identy是什么呢?对于操作的集合的所有t,应该有

accumulator.apply(identity, t) = t

是的,它就是离散数学里面提到的1元。对加法而言,1元就是0。


reduce操作总会返回新的值。然而,累积函数在每次处理流中的一个元素时也返回一个新的值。假设你要把流“reduce”成一个集合,你每添加一个元素处理就会产生一个新的结合,这显然是个性能上的缺憾,是很低效的。这时,你可以考虑更新已有的集合。这就是collect方法做的事情。


下一篇再介绍collect的使用细节。

Java:聚集操作