上节介绍了将流转成集合的方法(toCollections)和将元素转换成拼接字符串(joining)的方法,本节介绍几个求统计数值的方法,分别是
countingmaxByminBysummingInt/summingLong/summingDoubleaveragingInt/averagingLong/averagingDoublereducing
前三个都是借助reducing方法来实现的,后面的两个方法的实现思路和reducing很像,所以这节先介绍reducing铺垫一下
1.reducing
reducing方法可以将流中的元素进行累加,运算到第n个元素的时候,将前n-1个元素运算的中间结果和第n个元素进行运算,这个方法有三个重载
1.1reducing(T identity, BinaryOperator op)
这个方法接受两个参数,一个是初始值,如果流中的元素都没有的时候就会返回这个identity,另一个参数是BinaryOperator,这个是指定具体做什么运算
比如下面这个reduce的用法可以计算流中所有元素的总和
int total = IntStream.range(0, 10).reduce(0, Integer::sum);
返回的类型是Collector<T, ?, T>,即流中的元素和最终输出的元素是同一个类型T,中间容器的类型可以是任意
这个方法的源码是:
具体实现是:
return new CollectorImpl<>(boxSupplier(identity),(a, t) -> {a[0] = op.apply(a[0], t); },(a, b) -> {a[0] = op.apply(a[0], b[0]); return a; },a -> a[0],CH_NOID);
可以看到,它借助了一个长度为1的数组来作为中间的结果容器,这个的方法boxSupplier其实是提供这个数组,它的实现是:
private static <T> Supplier<T[]> boxSupplier(T identity) {return () -> (T[]) new Object[] {identity };}
只是提供了一个类型为T的长度为1的数组,数组里面的元素是identity,也就是传入的第一个参数,这个collector具体的组成是
为什么要借助这个数组,直接传一个值一直做运算不行吗?有这个疑惑的话需要考虑一下值对象是没办法做传递的,引用的对象才能将应用传递,这样这个对象的之才能修改,数组是一种引用对象,可以将地址传递,能在运算的时候对里面的值进行改变
1.2 reducing(BinaryOperator op)
这个方法接受一个参数,BinaryOperator,指定具体做什么运算,返回的类型是 Collector<T, ?, Optional>
也就是说,流中的元素是T,返回的结果是一个Optional,中间容器的类型可以是任意
这个方法的实现是借助了一个内部类OptionalBox,进行Optional类型的封装,这个方法已经集成了参数BinaryOperator op,进行accept方法时,如果是当前值是存在的就会进行运算
class OptionalBox implements Consumer<T> {T value = null;boolean present = false;@Overridepublic void accept(T t) {if (present) {value = op.apply(value, t);}else {value = t;present = true;}}}
这个方法的具体实现是:
return new CollectorImpl<T, OptionalBox, Optional<T>>(OptionalBox::new, OptionalBox::accept,(a, b) -> {if (b.present) a.accept(b.value); return a; },a -> Optional.ofNullable(a.value), CH_NOID);
返回的Collector的组成是:
1.3 reducing(U identity, Function<? super T, ? extends U> mapper,BinaryOperatorop)
这个方法接受三个参数,除了Identity 和BinaryOperator以外,还有一个mapper,将流中的元素T转成U,然后BinaryOperator和Identity都是U类型的,相当于映射完之后再调上面介绍的第一个reducing方法,因此这个方法的返回类型是Collector<T, ?, U> ,输入流中元素类型为T,映射到U之后再做运算,最后的返回结果是U类型
具体实现是:
return new CollectorImpl<>(boxSupplier(identity),(a, t) -> {a[0] = op.apply(a[0], mapper.apply(t)); },(a, b) -> {a[0] = op.apply(a[0], b[0]); return a; },a -> a[0], CH_NOID);
在第一个重载方法的基础上加了一个映射,从a[0] = op.apply(a[0], t)变成了a[0] = op.apply(a[0], mapper.apply(t));
以上就是reducing三个重载方法的详解,知道了这些内容,下面的几个方法就很容易理解了,先介绍三个借助它来实现的方法,分别是counting,maxBy,minBy,然后再介绍两个跟它的实现思路很像的方法summingInt, averagingInt,还有summingLong,averagingLong等是同理的,不会详细说明
2.counting()
这个方法的实现是
public static <T> Collector<T, ?, Long>counting() {return reducing(0L, e -> 1L, Long::sum);}
直接通过reducing的第三个重载方法实现,identity是0,map方法是e -> 1L,每个对象都会转换成1L,运算的方法是Long的加法,在这里T是流中的元素,U的类型是Long
3.minBy()
这个方法借助reducing的第二个重载方法返回一个,借助BinaryOperator.minBy(comparator)方法,这个方法接受一个comparator,也是返回一个BinaryOperator
public static <T> Collector<T, ?, Optional<T>>minBy(Comparator<? super T> comparator) {return reducing(BinaryOperator.minBy(comparator));}
BinaryOperator.minBy(comparator)的实现是:
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> pare(a, b) <= 0 ? a : b;}
所以collectors的minBy()方法相当于:
public static <T> Collector<T, ?, Optional<T>>minBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return reducing((a, b) -> pare(a, b) <= 0 ? a : b;);}
reducing接受的参数很明显是一个BinaryOperator,接受两个参数,返回更小的那一个
4.maxBy()
类似minBy,不过多赘述
5.summingInt(ToIntFunction<? super T> mapper)
该方法接受一个ToIntFunction方法,返回Integer,实现是:
public static <T> Collector<T, ?, Integer>summingInt(ToIntFunction<? super T> mapper) {return new CollectorImpl<>(() -> new int[1],(a, t) -> {a[0] += mapper.applyAsInt(t); },(a, b) -> {a[0] += b[0]; return a; },a -> a[0], CH_NOID);}
ToIntFunction顾名思义,就是接受泛型T,然后返回一个int的方法
int applyAsInt(T value);
所以返回的Collector的组成是:
其实也是借助了一个数组来实现,由于最终得到的结果是Integer,所以用一个长度为1的int数组来作为容器就行了
summingLong和summingDouble也是一样的道理,不过summingDouble要复杂一些,因为涉及到高八位和低八位的处理,所以用了长度为3的double数组,具体实现与Collectors的组成关联性不大,因此不做深入探究。
6. averagingInt(ToIntFunction<? super T> mapper)
这个方法用到了长度为2的int数组,一个用来存放总值sum,另一个用来存放计数count,average = sum/count,因此接受的是ToIntFunction类型的参数,返回Double类型。
具体实现:
public static <T> Collector<T, ?, Double>averagingInt(ToIntFunction<? super T> mapper) {return new CollectorImpl<>(() -> new long[2],(a, t) -> {a[0] += mapper.applyAsInt(t); a[1]++; },(a, b) -> {a[0] += b[0]; a[1] += b[1]; return a; },a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);}
averagingLong和averagingDouble也是类似的实现,在此省略。