用流收集数据

2019-02-11  本文已影响0人  PawsUp

CollectionCollectorCollect的区别:

可以使用一个收集器,Collector.maxBy来计算流中的最大值,这个收集器接收一个Comparator参数来比较流中的元素。Optional<Dish>是考虑到返回值为空时情况。

    Comparator<Dish> dishCaloriesComparator =
    Comparator.comparingInt(Dish::getCalories); 

    Optional<Dish> mostCalorieDish =
                 menu.stream()
                     .collect(maxBy(dishCaloriesComparator)); 
  //或者
  Optional<Dish> mostCalorieDish =
             menu.stream()
                 .collect(maxBy(comparingInt(Dish::getCalories)));

以上的所有收集器,都是一个可以用reducing工厂方法定义的归约过程的特殊情况。Collectors.reducing工厂方法是所有这些特殊情况的一般化。

  int totalCalories = menu.stream().collect(reducing(
                                   0, Dish::getCalories, (i, j) -> i + j));

reduce方法需要三个参数:

  1. 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
  2. 第二个参数就是转换函数,转换为int类型
  3. 数是一个BinaryOperator<T>,也就是一个BiFunction<T,T,T>,这就意味着他需要的函数必须接收两个参数,然后返回一个相同类型的值,将两个项目累积成一个同类型的值。

同样也有单参数reduceing工厂方法创建的容器收集器看作是三参数方法的特殊情况,它把流中的第一个项目作为起点,把恒等函数(即一个函数仅仅是返回其输入参数)作为一个转换函数。这也意味着,要是把单参数reducing收集器传递给空流的collect方法,收集器就没有起点;因此返回一个Optional对象。
例如:

  Optional<Dish> mostCalorieDish =
      menu.stream().collect(reducing(
         (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)); 

但是也可以不使用收集器也能执行相同操作,使用map映射,然后利用归约得到:

  int totalCalories =
      menu.stream().map(Dish::getCalories).reduce(Integer::sum).get(); 

就像流的任何单参数reduce操作一样,reduce(Integer::sum)返回的不是int而是Optional<Integer>,以便在空流的情况下安全地执行归约操作。然后只需用Optional对象中的get方法来提取里面的值就行了。在这种情况下使用get方法是安全的,只是因为已经确定菜肴流不为空。一般来说,使用允许提供默认值的方法,如orElseorElseGet来解开Optional中包含的值更为安全。更简单的方法就是映射到一个IntStream,然后调用sum方法。即:

  int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum(); 

可以看出来,其实收集器在某种程度上比Stream接口上直接提供的方法用起来更加复杂,但是使用收集器的好处在于能更容易重用和自定义。

返回的结果是一个两级 Map

  {MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
   FISH={DIET=[prawns], NORMAL=[salmon]},
   OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}} 

相当于一个二维表格:
image.png

可以把第二个groupingBy收集器传递给外层收集器来实现多级分组。但进一步说,传递给第一个groupingBy的第二个收集器可以是任何类型,而不一定是另一个groupingBy,可以是counting()maxBy()等,例如

(实际上普通的单参数groupingBy(f)实际上是groupingBy(f, toList())的简单写法)

  Map<Dish.Type, Long> typesCount = menu.stream().collect(
                     groupingBy(Dish::getType, counting())); 

  {MEAT=3, FISH=2, OTHER=4} //结果
  1. 把收集器的结果转换为另一种类型,可以使Collectors.collectingAndThen工厂方法返回的收集器

    例如:

     Map<Dish.Type, Dish> mostCaloricByType =
          menu.stream()
              .collect(groupingBy(Dish::getType,//分类函数
                      collectingAndThen(
                          maxBy(comparingInt(Dish::getCalories)),//包装后的收集器 Optional<T>
                      Optional::get))); //转换函数
     //结果
     {FISH=salmon, OTHER=pizza, MEAT=pork}  
    

    这个工厂方法接受两个参数——要转换的收集器以及转换函数,并返回另一个收集器。collect操作的最后一步就是将返回值用转换函数做一个映射。在这里,被包起来的收集器就是用maxBy建立的那个,而转换函数Optional::get则把返回的Optional中的值提取出来

  2. 与groupingBy联合使用的其他收集器---mapping

这个方法接受两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来。其目的是在累加之前对每个输入元素应用一个映射函数,这样就可以让接受特定类型元素的收集器适应不同类型的对象。

  Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
  menu.stream().collect(
     groupingBy(Dish::getType, mapping(
     dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET;
             else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
               else return CaloricLevel.FAT; },
      toCollection(HashSet::new) ))); 
  //结果
  {OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], FISH=[DIET, NORMAL]} 

Collectors类的静态工厂方法能够创建的所有收集器:(注意要配合终端方法collect()使用)

工厂方法 返回类型 用处
toList List<T> 把流中所有项目收集到一个 List
toSet Set<T> 把流中所有项目收集到一个 Set,删除重复项
toCollection Collection<T> 把流中所有项目收集到给定的供应源创建的集合
counting Long 计算流中元素的个数
summingInt Integer 对流中项目的一个整数属性求和
averagingInt Double 计算流中项目 Integer 属性的平均值
summarizingInt IntSummaryStatistics 收集关于流中项目Integer 属性的统计值,例如最大、最小、总和与平均值
joining String 连接对流中每个项目调用 toString 方法所生成的字符串
maxBy Optional<T> 一个包裹了流中按照给定比较器选出的最大元素的 Optional,或如果流为空则为 Optional.empty()
minBy Optional<T> 一个包裹了流中按照给定比较器选出的最小元素的 Optional,或如果流为空则为 Optional.empty()
reducing 归约操作产生的类型 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果应用转换函数
groupingBy Map<K, List<T>> 根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果 Map 的键
partitioningBy Map<Boolean,List<T>> 根据对流中每个项目应用谓词的结果来对项目进行分区

  1. 建立新的结果容器:supplier方法
    supplier方法必须返回一个结果为空的Supplier,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用。

     public Supplier<List<T>> supplier() {
          return () -> new ArrayList<T>();
     } 
     //或
     public Supplier<List<T>> supplier() {
          return ArrayList::new;
     } 
    
  2. 将元素添加到结果容器:accumulator方法
    accumulator方法会返回执行归约操作的函数。当遍历到流中第n个元素时,这个函数执行时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目),还有第n个元素本身。该函数将返回void,因为累加器是原位更新,即函数的执行改变了它的内部状态以体现遍历的元素的效果。

     public BiConsumer<List<T>, T> accumulator() {
            return (list, item) -> list.add(item);
     } 
      //或
     public BiConsumer<List<T>, T> accumulator() {
            return List::add;
     }
    
  3. 对结果容器应用最终转换:finisher方法
    在遍历完流后,finisher方法必须返回在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果。通常,累加器对象恰好符合预期的最终结果,因此无需进行转换。所以finisher方法只需返回identity函数:

     public Function<List<T>, List<T>> finisher() {
         return Function.identity();
     } 
    
image.png
  1. 合并两个结果容器:combiner方法
    combiner方法会返回一个供归约操作使用的函数,它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并,这样,就可以做到对流进行并行归约了//???

     public BinaryOperator<List<T>> combiner() {
          return (list1, list2) -> {
                list1.addAll(list2);
                return list1; }
     } 
    
  2. characteristics方法
    返回一个不可变的Characteristics集合,它定义了收集器的行为——尤其是关于流是否可以并行归约,以及可以使用哪些优化的提示。

    Characteristics是一个包含三个项目的枚举:

    • UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。

    • CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。

    • IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。

上一篇下一篇

猜你喜欢

热点阅读