Java8 Streams用法总结大全 之 Collector用法详解

1、前言

  在《Java8 Streams用法总结大全 之 Stream中的常见操作》中,我们已经学习了Stream中的常用操作,其中也提到了collect()的用法,当时只是通过入参Collectors.toList()实现了把Stream转为List集合的功能,其实collect()还有很多其他的用法,入参就是实现了Collector接口的对象,同时Collectors可以看作是Collector的工厂类,其为我们提供了非常多的内建Collector的方法,具体如何使用,我们一起来学习一下。

2、Collector接口

  在前面提到的collect()方法,其入参就是一个实现了Collector接口的对象。其实在Stream中,collect有两个重载方法,如下所示:

<R, A> R collect(Collector<? super T, A, R> collector);

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

  其中T, A, R三个泛型类型,其实对应了Collector接口中的三个泛型类型,而第二个重载方法,其实对应了Collector接口中的三个接口方法是返回类型,学习了Collector接口,我们就会明白其中的含义。

2.1、Collector接口定义
public interface Collector<T, A, R> {
}

  Collector是一个泛型接口,有三个泛型参数分别是T、A、R,其含义如下:

  • T 代表着Stream元素的数据类型,比如Production、String、Integer等。
  • A 代表着累加器的数据类型,在Stream collect方法源码中甚至将其命名为容器,通常情况下,经过了collection操作之后的部分数据会被存放在该累加器中或者容器中。
  • R 代表着collect方法最终返回的数据类型。
2.2、Collector接口定义方法

  Collector接口定义了五个接口,每个接口方法的作用,请看方法注释,具体如下所示:

public interface Collector<T, A, R> {
	
	/**
	 *该方法将返回一个类型为A的Supplier,该方法会创建一个元素容器,该容器在accumulator()方法中将会被用到,主要用于收集累加器计算的数据结果
	 */
	Supplier<A> supplier();
	/**
	 *累加器方法是比较关键的方法,该方法会部分(在并行流中)运算或者全部计算(在串行流中)Stream流经的元素,并且将其存入supplier方法构造出来的容器中。
	 */
	BiConsumer<A, T> accumulator();
	/**
	 *该方法主要用于在并行流中进行结果的整合操作,在并行流中,每一个子线程都在执行部分数据的累加器方法,最后的结果该如何自处呢?当然是需要将其进行整合(分而治之,Fork Join的思想),那么该方法的泛型参数与supplier()方法一致也就很容易理解了。
	 */
	BinaryOperator<A> combiner();
	/**
	 * 当所有的计算完成之后,该方法将被用于做进一步的transformation操作,比如将int类型转换为long类型,同时该方法也是整个Collector接口在Stream collect操作中最后一个被调用的方法。
	 */
	Function<A, R> finisher();
	/**
	 * 该方法主要用于定义Collector的特征值,包含了CONCURRENT、UNORDERED和IDENTITY_FINISH三个类型
	 */
	Set<Characteristics> characteristics();
}

  Collector接口方法在串行流中的执行过程,如下:
在这里插入图片描述
  如上图所示,在串行流中,其中combiner方法将不会被使用到,因为不存在子线程子任务数据的合并动作,所有的操作将直接由单线程来完成。

  Collector接口方法在并行流中的执行过程,如下所示:
在这里插入图片描述
 &emp;如上图所示,和串行流相比,在这里当分支流程执行完成后,需要调用combiner方法进行结果的合并,然后再调用finisher方法返回结果。

2.3、Collector对象的创建方法

  在Collector接口中,还定义了两个of()方法,用于创建Collector类型的对象,本质上还是由Collectors工厂类创建的Collector对象,具体实现如下:

public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = (characteristics.length == 0)
                                  ? Collectors.CH_ID
                                  : Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
                                                                           characteristics));
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
    }

	public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) {
        Objects.requireNonNull(supplier);
        Objects.requireNonNull(accumulator);
        Objects.requireNonNull(combiner);
        Objects.requireNonNull(finisher);
        Objects.requireNonNull(characteristics);
        Set<Characteristics> cs = Collectors.CH_NOID;
        if (characteristics.length > 0) {
            cs = EnumSet.noneOf(Characteristics.class);
            Collections.addAll(cs, characteristics);
            cs = Collections.unmodifiableSet(cs);
        }
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
    }

3、Collectors类

  Collectors类其实就是Collector类的工厂类,内置了很多遍历的方法,这里我们就分别看一下其中都有哪些类型的用法:

3.1、Collectors.averaging类型方法(求平均值)
  • averagingInt(ToIntFunction<? super T> mapper):将Stream的元素T转换为int类型,然后计算其平均值。
  • averagingLong(ToLongFunction<? super T> mapper):将Stream的元素T转换为long类型,然后计算其平均值。
  • averagingDouble(ToDoubleFunction<? super T>mapper):将Stream的元素T替换为double类型,然后计算其平均值。
public static void averaging(){
  Stream<Production> stream = initData().stream();
    //求平均值
    Double average = stream.collect(Collectors.averagingDouble(Production::getPrice));
    System.out.println("average:" + average);
}
//初始数据的方法,后续使用的均是该方法
public static List<Production> initData(){
    List<Production> list = new ArrayList<>();
    list.add(new Production("T-Shirt",43.34d));
    list.add(new Production("cloth",99.99d));
    list.add(new Production("shoe",123.8d));
    list.add(new Production("hat",26.5d));
    list.add(new Production("cloth",199.99d));
    list.add(new Production("shoe",32.5d));
    return list;
}
3.2、 Collectors.collectingAndThen方法

  该方法的主要作用是对当前Stream元素经过一次Collector操作之后,对结果再次进行transformation操作。

public static void  collectingAndThen(){
     Stream<Production> stream = initData().stream();
      //求商品总价,然后判断是否超过800元
      Boolean average = stream.collect(Collectors.collectingAndThen(
              Collectors.summingDouble(Production::getPrice),
              p -> p > 800
      ));
      System.out.println("是否超额:" + average);
  }
3.3、Collectors.counting方法

  counting方法所创建的Collector,其主要用于返回Stream中元素的个数,当Stream中没有任何元素时返回0,counting方法在Stream collect操作中的效果实际上是等价于Stream的count方法,但是由于counting方法返回的是一个Collector,因此它可以应用于其他的Collectors方法中。

public static void  counting() {
    Stream<Production> stream = initData().stream();
     Long count = stream.collect(Collectors.counting());
     System.out.println("count:" + count);
 }
3.4、Collectors.mapping方法
<T, U, A, R> Collector<T, ?, R>mapping(Function<? super T, ? extends U> mapper,Collector<? super U, A, R> downstream)

  首先Function函数将Stream中的类型为T的元素transformation成U类型,紧接着downstream collector将处理元素类型为U的Stream。

public static void  mapping() {
     Stream<Production> stream = initData().stream();
     //先转化成Stream<Double>类型,然后再计算合计总价
     Double total = stream.collect(Collectors.mapping(
         Production::getPrice,
             Collectors.summingDouble(Double::doubleValue)
     ));
     System.out.println("total:" + total);
 }
3.5、 Collectors.joining方法

  Collectors的joining方法主要用于将Stream中的元素连接成字符串并且返回,Collectors的joining()方法如有下三种重载形式。

  • joining():将Stream中的元素连接在一起,中间没有任何符号对其进行分隔。
  • joining(CharSequence delimiter):将Stream中的元素连接在一起,元素与元素之间将用delimiter进行分割。
  • joining(CharSequence delimiter,CharSequence prefix,CharSequence suff ix):将Stream中的元素连接在一起,元素与元素之间将用delimiter进行分割;除此之外,最后的返回结果还将会被pref ix与suff ix包裹。
public static void  joining() {
    Stream<Production> stream = initData().stream();
     //获取商品名称,拼接字符串,输出:“<T-Shirt,cloth,shoe,hat,cloth,shoe>”
     String str = stream.collect(
             Collectors.mapping(
                     Production::getName,
                     Collectors.joining(",","<",">")
             )
     );
     System.out.println("str:" + str);
 }
3.6、Collectors.summing方法
  • summingInt(ToIntFunction<? super T> mapper):将Stream的元素T转换为int类型,然后对所有值求和。
  • summingDouble(ToDoubleFunction<? super T>mapper):将Stream的元素T转换为double类型,然后对所有值求和。
  • summingLong(ToLongFunction<? super T> mapper):将Stream的元素T转换为long类型,然后对所有值求和。
public static void  summing() {
    Stream<Production> stream = initData().stream();
    //统计商品总价格
    Double total = stream.collect(Collectors.summingDouble(Production::getPrice));
    System.out.println("total:" + total);
}
3.7、Collectors获取最大值最小值的方法

  Collectors提供了可以获取Stream中最大元素和最小元素的Collector。

  • maxBy(Comparator<? super T> comparator):根据Comparator获取Stream中最大的那个元素。
  • minBy(Comparator<? super T> comparator):根据Comparator获取Stream中最小的那个元素。
public static void  maxMin() {
     Stream<Production> stream = initData().stream();
     Stream<Production> stream2 = initData().stream();
     //最高价商品
     Optional<Production> optional = stream.collect(Collectors.maxBy((o1, o2) -> (int)(o1.getPrice() - o2.getPrice())));
     System.out.println("name:" + optional.get().getName() + "-price:" + optional.get().getPrice());
     //最低价商品
     Optional<Production> optional2 = stream2.collect(Collectors.minBy((o1, o2) -> (int)(o1.getPrice() - o2.getPrice())));
     System.out.println("name:" + optional2.get().getName() + "-price:" + optional2.get().getPrice());
 }
3.8、Collectors.summarizing方法

  前面学习了Collectors的averaging和summing,如何用counting方法创建对应用途的Collector。而summarizing方法创建的Collector则会集averaging、summing、counting于一身,并且提供了更多额外的方法,summarizing提供了三种汇总方式,如下所示:

  • summarizingInt(ToIntFunction<? super T> mapper):将Stream元素转换为int类型,并且进行汇总运算,该Collector的返回值为IntSummaryStatistics类型。
  • summarizingLong(ToLongFunction<? super T>mapper):将Stream元素转换为long类型,并且进行汇总运算,该Collector的返回值为LongSummaryStatistics类型。
  • summarizingDouble(ToDoubleFunction<? super T>mapper):将Stream元素转换为double类型,并且进行汇总运算,该Collector的返回值为DoubleSummaryStatistics类型。
public static void  summarizing() {
   Stream<Production> stream = initData().stream();
    //同时包含了averaging、summing和counting的值,输出结果:DoubleSummaryStatistics{count=6, sum=526.120000, min=26.500000, average=87.686667, max=199.990000}
    DoubleSummaryStatistics statistics = stream.collect(Collectors.summarizingDouble(Production::getPrice));
    System.out.println("statistics:" + statistics);
}
3.9、Collectors输出到其他容器的方法

  Stream通过若干intermediate操作之后,可以执行collect操作将Stream中的元素输出汇总至其他容器中,比如Set、List、Map等。

  • toSet():将Stream中的元素输出到Set中
  • toList():将Stream中的元素输出到List中
  • toMap():将Stream中的元素输出到Map中,Collectors提供了toMap的三种重载形式。
    1. toMap(Function<? super T, ? extends K>keyMapper,Function<? super T, ? extends U>valueMapper):该方法需要两个Function参数,第一个参数应用于map key的mapper操作,第二个参数应用于value的mapper操作。
    2. toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U>valueMapper,BinaryOperator<U> mergeFunction):该toMap方法和第一个重载方法相比,多了一个BinaryOperator参数,该参数主要那个用于解决当Key值出现冲突时的merge方法,通过该函数可以创建类似grouping的效果。
    3. toMap(Function<? super T, ? extends K>keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier):与第二个重载方法相比,多了一个Supplier参数,该参数用来配置创建返回的Map类型(前面两个方法返回的都是HashMap类型)。
  • toCollection(Supplier<C> collectionFactory) 将Stream中的元素输出到collectionFactory指定的Collection类型中。
  • toConcurrentMap(Function<? super T, ? extends K>keyMapper,Function<? super T, ? extends U>valueMapper) 和toMap()方法类似,只不过这里使用的是ConcurrentHashMap对象,提供了线程安全的作用。
  • toConcurrentMap(Function<? super T, ? extends K>keyMapper,Function<? super T, ? extends U>valueMapper,BinaryOperator<U> mergeFunction) 和toMap()方法类似,只不过这里使用的是ConcurrentHashMap对象,提供了线程安全的作用。
  • toConcurrentMap(Function<? super T, ? extends K>keyMapper,Function<? super T, ? extends U>valueMapper,BinaryOperator<U>mergeFunction,Supplier<M> mapSupplier) 和toMap()方法类似,只不过这里使用的是ConcurrentHashMap对象,提供了线程安全的作用。
public static void  toCollection() {
    Stream<Production> stream = initData().stream();
     Stream<Production> stream2 = initData().stream();
     //把商品价格转成List输出
     List<Double> list = stream.map(Production::getPrice).collect(Collectors.toList());
     System.out.println("list:" + list);
     //把商品按照名字分类,每类用List存成数据,最后返回TreeMap对象,其中key是商品名称,value是商品的List集合
     TreeMap<String,List> treeMap = stream2.collect(Collectors.toMap(
             Production::getName,
             Arrays::asList,
             (p1,p2) ->{
                 List<Production> mergeList = new ArrayList<>();
                 mergeList.addAll(p1);
                 mergeList.addAll(p2);
                 return mergeList;
             },
             TreeMap::new
     ));
     System.out.println("treeMap:" + treeMap);
 }
3.10、Collectors.partitioningBy方法

  该方法会将Stream中的元素分为两个部分,以Map<Boolean,?>的形式作为返回值,Key为True代表一部分;Key为False代表另外一部分。partitioningBy有两个重载方法,如下所示:

  • partitioningBy(Predicate<? super T> predicate):根据Predicate的判断,将Stream中的元素分为两个部分,最后的返回值为Map<Boolean,List<?>>。
  • partitioningBy(Predicate<? super T>predicate,Collector<? super T, A, D> downstream):和第一个重载方法相比,多了一个downstream参数,这让该重载方法灵活强大许多了,比如,我们可以对每一个分区的元素再次进行其他Collector的操作运算。
public static void collectPartition(){
    Stream<Production> stream = initData().stream();
    Stream<Production> stream2 = initData().stream();
    //分区,把大于100和小于等于100的分成两份,其中key=false的对应=<100,key=true对应>100,
    // 通过Collectors.toSet()让每个部分的集合转成了Set,默认是List
    Map<Boolean,Set<Production>> map = stream.collect(
            Collectors.partitioningBy(
                    item -> item.getPrice() > 100,
                    Collectors.toSet()
            )
    );
    System.out.println(map);
    //分区,把大于100和小于等于100的分成两份,其中key=false的对应=<100,key=true对应>100,
    // 然后针对分区后的结果,对每个分区计算汇总值,返回类型为Map<Boolean,Double>
    Map<Boolean,Double> map2 = stream2.collect(
            Collectors.partitioningBy(
                    item -> item.getPrice() > 100,
                    Collectors.summingDouble(Production::getPrice)
            )
    );
    System.out.println(map2);
}
3.11、 Collectors.groupingBy方法

  groupingBy方法类似于关系型数据库中的分组操作,其主要作用是根据classifier(分类器)对Stream中的元素进行分组,groupingBy方法在Collectors中提供了如下几种重载形式:

  • groupingBy(Function<? super T, ? extends K> classifier):根据分类器函数对Stream中的元素进行分组,返回结果类型为:Map<K, List<T>>。
  • groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream):与第一个重载方法相比,多了一个downstream参数,用于运算操作,可以改变返回集合的类型。
  • groupingBy(Function<? super T, ? extends K> classifier,Supplier mapFactory, Collector<? super T, A, D>downstream):与第二个重载方法先比,多了一个提供构造返回Map类型的Supplier,在前两个groupingBy方法中返回的Map为HashMap,在该方法中,开发者可以指定Map的其他实现类。

  除此之外,Collectors还提供了其他三个groupingByConcurrent的重载形式,返回结果为线程安全的、支持并发的Map实现ConcurrentHashMap,其具体用法和原理与本节中介绍的三个重载方法类似。

public static void collectGroup() {
        Stream<Production> stream = initData().stream();
        Stream<Production> stream2 = initData().stream();
        Stream<Production> stream3 = initData().stream();
        //分组统计各类的总价
        Map<String,Double> map = stream.collect(Collectors.groupingBy(
                Production::getName, Collectors.summingDouble(Production::getPrice)
        ));
        System.out.println(map);

        //分组统计
        Map<String,List<Production>> map2 = stream2.collect(Collectors.groupingBy(
                Production::getName
        ));
        System.out.println(map2);

        //分组统计
        TreeMap<String,Set<Production>> map3 = stream3.collect(Collectors.groupingBy(
                Production::getName,//根据name进行分组
                TreeMap::new, //返回TreeMap,替换HashMap
                Collectors.toSet()//每个分组中的集合使用Set,默认使用List
        ));
        System.out.println(map3);
    }
3.12、 Collectors.reducing方法

  与Stream的reduce操作非常类似,Collectors的reducing方法也将创建一个用于对Stream中的元素进行reduce计算的Collector,该操作在Collectors中提供了三个重载方法:

  • reducing(BinaryOperator<T> op):给定一个BinaryOperator函数式接口,对Stream中的每一个元素进行计算,但是该reducing创建的Collector其返回值将是一个类型与Stream中元素类型一致的Optional。
  • reducing(T identity, BinaryOperator op):该方法的作用与上面的reducing类似,只不过增加了一个identity的参数,该参数会纳入BinaryOperator函数的运算之中,除此之外当该Stream为空时,reducing将会直接返回该identity。
  • reducing(U identity,Function<? super T, ? extends U>mapper, BinaryOperator<U> op):前两个reducing方法只能返回与Stream元素类型一样的结果或者Optional,该重载方法允许开发者返回不同于其他类型的结果,因为有了mapper函数的加持。
public static void reducing() {
        Stream<Production> stream = initData().stream();
        Stream<Production> stream2 = initData().stream();
        Stream<Production> stream3 = initData().stream();

        //获取价格最高的产品
        Optional<Production> production = stream.collect(Collectors.reducing(
                (p1,p2) -> p1.getPrice() > p2.getPrice() ? p1:p2
        ));
        System.out.println("production:" + production.get());
        //获取价格最高的产品,增加一个初始值
        Production production2 = stream2.collect(Collectors.reducing(
                new Production("kuzi",2000),
                (p1,p2) -> p1.getPrice() > p2.getPrice() ? p1:p2
        ));
        System.out.println("production2:" + production2);
        //获取商品总价
        Double tatal = stream3.collect(Collectors.reducing(
                0d,
                Production::getPrice,
                (p1,p2) -> p1 + p2
        ));
        System.out.println("tatal:" + tatal);
    }

4、自定义Collector

  Collectors提供的静态方法几乎可以满足我们对Collector的所有需要,如果你觉得有些Collector无法满足你的需求,那么完全可以自行扩展,同时我们也可以通过自定义Collector,更加熟悉Collector的用法。

  这里我们实现一个类似toList方法的功能,首先,需要实现Collector接口,具体实现如下:

/**
 * 自定义Collector
 */
public class QriverCollector<T> implements Collector<T, List<T>, List<T>> {
    /**
     * 容器
     * @return
     */
    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    /**
     * 累加器
     * @return
     */
    @Override
    public BiConsumer<List<T>,T> accumulator() {
        return List::add;
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return (p,l) ->{
            p.addAll(l);
            return p;
        };
    }

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

    @Override
    public Set<Characteristics> characteristics() {
        return EnumSet.of(Characteristics.UNORDERED,Characteristics.CONCURRENT,Characteristics.IDENTITY_FINISH);
    }
}

  然后,我们实际使用时如下所示:

public static void custom() {
    Stream<Production> stream = initData().stream();
    List<Production> list = stream.collect(new QriverCollector<>());
    System.out.println("list:" + list);
}

  至此,我们就了解了Collector的一些用法,后续再继续学习关于并发流的一些知识点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值