Java8 函数式编程 笔记-收集器基本用法

       参考并建议阅读《Java8函数式编程》 /《java 8 实战》
-------------------------------------------------------------------------------------------------------------------------------------------------------------
     收集器,一种通用的,从流生成的复杂的值的结构。只要将它传给collect方法,所有的流都可以使用它了。标准类库已经提供了一些有用的收集器,java.util.stream.Collectors类中提供了相关的方法。

使用收集器

   转换成其他集合

      有一些收集器可以生成其他集合,比如,toList, toSet, toCollection. 总有一些时候,需要生成一个集合:
      1)已有代码是为集合编写的,因此需要将流转换成集合传入
      2)在集合上进行一系列链式操作后,最终希望生成一个值
      3)写单元测试时,需要对某个具体的集合做断言
      调用toList或者toSet方法时,不需要指定具体的类型,Stream类库在背后已经自动挑选出了合适的类型。
      如果希望使用一个特定的集合收集值,比如希望使用TreeSet,而不是框架指定的Set, 可以使用toCollection接受一个函数作为参数,来创建集合。
IntStream.range(0,5).boxed().collect(Collectors.toCollection(TreeSet::new));

   转换成值

       可以利用收集器让流生成一个值。如maxBy,minBy和averagingInt.
       
IntStream.range(0,5).boxed().collect(Collectors.maxBy(Comparator.comparing(x->x)))

   数据分块

      另外一个常用的流操作是将其分解成两个集合。partitioningBy,接受一个流,并将其分成两部分,使用Predicate对象判断一个元素应该属于哪个部分,并根据布尔值返回一个Map到列表。因此,对于true List中的元素,Predicate返回true,对于其他List中的元素,Predicate返回false。如下图所示。
       

   数据分组

   数据分组是一种更自然的分割数据操作,与将数据分成true和false两部分不同,可以使用任意值对数据分组。groupingBy收集器接受一个分类函数,用来对数据分组,就像partitionBy一样,接受一个Predicate对象将数据分成true和false两部分。用的分类器时一个Function对象,和map操作用到的一样。
       
                                                         
                                                                表 1 Collectors类的静态工厂方法

工厂方法

返回类型

用于

 toList

List<T>

把流中所有项目收集到一个List

使用示例: List<Dish> dishes = menuStream.collect(toList());

toSet

Set<T>

把流中所有项目收集到一个 Set,删除重复项

使用示例: Set<Dish> dishes = menuStream.collect(toSet());

toCollection

Collection<T>

把流中所有项目收集到给定的供应源创建的集合

使用示例: Collection<Dish> dishes = menuStream.collect(toCollection(),ArrayList::new);

counting

Long

计算流中元素的个数

使用示例: long howManyDishes = menuStream.collect(counting());

summingInt

Integer

对流中项目的一个整数属性求和

使用示例: int totalCalories = menuStream.collect(summingInt(Dish::getCalories))

averagingInt

Double

计算流中项目 Integer 属性的平均值

使用示例: double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));

summarizingInt

IntSummaryStatistics

收集关于流中项目 Integer 属性的统计值,例如最大、最小、总和与平均值

使用示例 :               

IntSummaryStatistics menuStatistics=menuStream.collect(summarizingInt(Dish::getCalories));

joining

String

连接对流中每个项目调用 toString 方法所生成的字符串

使用示例: String shortMenu =menuStream.map(Dish::getName).collect(joining(", "));

maxBy

Optional<T>

一个包裹了流中按照给定比较器选出的最大元素的 Optional,或如果流为空则为 Optional.empty()

使用示例:

 Optional<Dish> fattest =menuStream.collect(maxBy(comparingInt(Dish::getCalories)));

minBy

Optional<T>

一个包裹了流中按照给定比较器选出的最小元素的 Optional,或如果流为空则为 Optional.empty()

使用示例:

Optional<Dish> lightest =menuStream.collect(minBy(comparingInt(Dish::getCalories)));

reducing

归约操作产生的类型

从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值

使用示例:

int totalCalories =menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));

collectingAndThen

转换函数返回的类型

包裹另一个收集器,对其结果应用转换函数

使用示例:

int howManyDishes =menuStream.collect(collectingAndThen(toList(), List::size)

groupingBy

Map<K, List<T>>

根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果 Map 的键

使用示例:

Map<Dish.Type,List<Dish>>dishesByType =menuStream.collect(groupingBy(Dish::getType));

partitioningBy

Map<Boolean,List<T>>

根据对流中每个项目应用谓词的结果来对项目进行分区

使用示例:

Map<Boolean,List<Dish>> vegetarianDishes

=menuStream.collect(partitioningBy(Dish::isVegetarian));

组合收集器

各种收集器已经很强大,但是仍然可以将它们组合起来使用,如下面这个例子:
    public Map<Artist,Long> numberOfAlbums(Stream<Album> albums){
        return albums.collect(groupingBy(album->album.getMainMusician(),counting()));
    }

 groupingBy先将元素分成块,每块都与分类函数getMainMusician提供的键值相关联,然后使用下游的另一个收集器收集每块中的元素,最后将结果映射为一个Map.还可以看下面这个例子mapping收集器和map方法一样,接受一个Function对象作为参数,如:
    public Map<Artist,List<String>> numberOfAlbums(Stream<Album> albums){
        return albums.collect(groupingBy(album->album.getMainMusician(),mapping(Album::getName,toList())));
    }

这两个例子都使用了第二个收集器,用以收集最终结果额一个子集,这样的收集器叫下游收集器。主收集器中会用到下游收集器,这种方式叫做组合使用收集器。

收集器接口

Collector接口包含了一系列方法,为实现具体的归约操作范本,通过实践Collector接口我们可以定制自己的收集器。
 Collector接口:
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<java.util.stream.Collector.Characteristics> characteristics();
}

T 是流中要收集的项目泛型
A 是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
R 是收集操作得到的对象的类型
例如,实现一个ToListCollector<T>类,将Stream<T>中所有元素收集到一个List<T>里,签名:
public class ToListCollector<T> implements Collector<T, List<T>, List<T>>

下面说明一下Collector的各个方法:
1. 建立新的结果容器: supplier方法
  supplier方法必须返回一个结果为空的Supplier,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用。很明显,对于将累加器本身作为结果返回的收集器,比如我们的ToListCollector,在对空流执行操作的时候,这个空的累加器也代表了收集过程的结果。在我们的ToListCollector中,supplier返回一个空的List,如下所示:
public Supplier<List<T>> supplier() {
     return () -> new ArrayList<T>();
}

2. 将元素添加到结果容器: accumulator方法
   accumulator方法会返回执行归约操作的函数。当遍历到流中第n个元素时,这个函数执行时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目), 还有第n个元素本身。该函数将返回void,因为累加器是原位更新,即函数的执行改变了它的内部状态以体现遍历的元素的效果。对于ToListCollector,这个函数仅仅会把当前项目添加至已经遍历过的项目的列表: 

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

public Function<List<T>, List<T>> finisher() {
     return Function.identity();
}

4. 合并两个结果容器: combiner方法
四个方法中的最后一个——combiner方法会返回一个供归约操作使用的函数,它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并。对于toList而言,这个方法的实现非常简单,只要把从流的第二个部分收集到的项目列表加到遍历第一部分时得到的列表后面就行了: 

public BinaryOperator<List<T>> combiner() {
     return (list1, list2) -> {
              list1.addAll(list2);
              return list1; }
}

                                    
                                                                        顺序归约

                 
                                                               combiner并行化归约
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值